├── .env.example ├── public ├── robots.txt ├── favicon.png ├── audio │ ├── move.ogg │ └── capture.ogg ├── img │ ├── tan_logo.png │ ├── default-img.png │ ├── alpha_banner.png │ ├── chesspieces │ │ └── wikipedia │ │ │ ├── bB.png │ │ │ ├── bE.png │ │ │ ├── bH.png │ │ │ ├── bK.png │ │ │ ├── bN.png │ │ │ ├── bP.png │ │ │ ├── bQ.png │ │ │ ├── bR.png │ │ │ ├── bV.png │ │ │ ├── gB.png │ │ │ ├── gK.png │ │ │ ├── gN.png │ │ │ ├── gP.png │ │ │ ├── gQ.png │ │ │ ├── gR.png │ │ │ ├── rB.png │ │ │ ├── rK.png │ │ │ ├── rN.png │ │ │ ├── rP.png │ │ │ ├── rQ.png │ │ │ ├── rR.png │ │ │ ├── wB.png │ │ │ ├── wE.png │ │ │ ├── wH.png │ │ │ ├── wK.png │ │ │ ├── wN.png │ │ │ ├── wP.png │ │ │ ├── wQ.png │ │ │ ├── wR.png │ │ │ └── wV.png │ └── Red-Seamless-Wood-Texture-for-Photoshop.jpg └── style │ ├── chessboard-0.3.0.min.css │ └── chessboard-0.3.0.css ├── .babelrc ├── mongod ├── server ├── db │ └── mongoose.js ├── utils │ └── utils.js ├── middleware │ └── authenticate.js ├── sockets │ ├── sockets.ts │ └── listeners │ │ ├── connection.ts │ │ └── matchmaking.ts ├── game_models │ ├── ratings │ │ └── Ratings.ts │ ├── matchmaking │ │ └── QueueItem.ts │ ├── players │ │ ├── AI.ts │ │ └── Player.ts │ ├── games │ │ ├── Standard.ts │ │ ├── FullhouseChess.ts │ │ ├── SChess.ts │ │ └── CrazyHouse.ts │ └── rooms │ │ └── Message.ts ├── models │ ├── schess.js │ ├── standard.js │ ├── crazyhouse.js │ ├── fullhouse-chess.js │ ├── crazyhouse960.js │ └── fourgame.js ├── router.js ├── controllers │ ├── authentication.js │ └── games.js ├── server.ts ├── engine │ ├── TwoEngine.ts │ ├── CrazyEngine.ts │ ├── Engine.ts │ └── FourEngine.ts └── services │ └── passport.js ├── .gitignore ├── tsconfig.json ├── test ├── components │ └── app_test.js └── test_helper.js ├── src ├── reducers │ ├── rooms_reducer.js │ ├── new_game_reducer.js │ ├── profile_reducer.js │ ├── auth_reducer.js │ └── index.js ├── utils │ ├── jwtHelper.js │ └── index.js ├── components │ ├── auth │ │ ├── login.jsx │ │ ├── require_auth.js │ │ ├── login_form.jsx │ │ └── signup_form.jsx │ ├── search_bar.js │ ├── room │ │ ├── room_user_list.jsx │ │ ├── browser_notification.jsx │ │ ├── room_settings.jsx │ │ └── room.jsx │ ├── chat_message │ │ ├── message_list.jsx │ │ └── message_list_item.jsx │ └── create_game │ │ └── new_game_modal_room_options.jsx ├── routes.js ├── containers │ ├── app.js │ ├── pgn │ │ ├── pgn_buttons.jsx │ │ ├── pgn_move.jsx │ │ ├── pgn_first_button.jsx │ │ ├── pgn_viewer.jsx │ │ ├── pgn_last_button.jsx │ │ ├── pgn_prev_button.jsx │ │ └── pgn_next_button.jsx │ ├── message_send.jsx │ ├── notification_handler.jsx │ ├── room_info.jsx │ ├── board │ │ ├── promotion_selector.jsx │ │ └── schess_hand.jsx │ ├── existing_room_list.jsx │ ├── new_game.jsx │ ├── user │ │ └── player_list.jsx │ ├── room_viewer.jsx │ ├── presets │ │ └── quick_match.jsx │ └── auth_card.jsx ├── actions │ ├── types.js │ ├── create_game.js │ └── user.js ├── index.jsx └── middleware │ └── voice.js ├── README.md ├── LICENSE.MD ├── webpack.config.js.template ├── common └── Clock.js └── package.json /.env.example: -------------------------------------------------------------------------------- 1 | NODE_ENV="staging" -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: * 2 | Disallow: 3 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["react", "es2015", "stage-1"] 3 | } 4 | -------------------------------------------------------------------------------- /mongod: -------------------------------------------------------------------------------- 1 | mongod --bind_ip=$IP --dbpath=data --nojournal --rest "$@" 2 | -------------------------------------------------------------------------------- /public/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/favicon.png -------------------------------------------------------------------------------- /public/audio/move.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/audio/move.ogg -------------------------------------------------------------------------------- /public/audio/capture.ogg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/audio/capture.ogg -------------------------------------------------------------------------------- /public/img/tan_logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/tan_logo.png -------------------------------------------------------------------------------- /public/img/default-img.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/default-img.png -------------------------------------------------------------------------------- /public/img/alpha_banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/alpha_banner.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bB.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bE.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bH.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bK.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bP.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bQ.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bR.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/bV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/bV.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gB.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gK.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gN.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gP.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gQ.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/gR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/gR.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rB.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rK.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rN.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rP.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rQ.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/rR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/rR.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wB.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wB.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wE.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wH.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wH.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wK.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wK.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wP.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wP.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wQ.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wQ.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wR.png -------------------------------------------------------------------------------- /public/img/chesspieces/wikipedia/wV.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/chesspieces/wikipedia/wV.png -------------------------------------------------------------------------------- /public/img/Red-Seamless-Wood-Texture-for-Photoshop.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/johnnyvf24/hellochess-v2/HEAD/public/img/Red-Seamless-Wood-Texture-for-Photoshop.jpg -------------------------------------------------------------------------------- /server/db/mongoose.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | mongoose.Promise = global.Promise; 4 | mongoose.connect('mongodb://localhost:27017/HelloChess'); 5 | 6 | module.export = mongoose; 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | bundle.js 3 | bundle.js.map 4 | npm-debug.log 5 | config.js 6 | /playground 7 | .env 8 | webpack.config.js 9 | current_errors 10 | .forever 11 | engine/bin 12 | data 13 | executables 14 | 15 | # IntelliJ 16 | *.iml 17 | /.idea 18 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "noImplicitAny": false, 6 | "sourceMap": false, 7 | "typeRoots": ["node_modules/@types"], 8 | "allowJs": true 9 | } 10 | } -------------------------------------------------------------------------------- /server/utils/utils.js: -------------------------------------------------------------------------------- 1 | function mapObject(object, callback) { 2 | return Object.keys(object).map(function (key) { 3 | return callback(key, object[key]); 4 | }); 5 | } 6 | 7 | module.exports.ab2str = function(buf) { 8 | return String.fromCharCode.apply(null, new Uint16Array(buf)); 9 | }; 10 | 11 | module.exports.mapObject = mapObject; 12 | -------------------------------------------------------------------------------- /test/components/app_test.js: -------------------------------------------------------------------------------- 1 | import { renderComponent , expect } from '../test_helper'; 2 | import App from '../../src/components/app'; 3 | 4 | describe('App' , () => { 5 | let component; 6 | 7 | beforeEach(() => { 8 | component = renderComponent(App); 9 | }); 10 | 11 | it('renders something', () => { 12 | expect(component).to.exist; 13 | }); 14 | }); 15 | -------------------------------------------------------------------------------- /src/reducers/rooms_reducer.js: -------------------------------------------------------------------------------- 1 | import {LOGOUT_SUCCESS} from '../actions/index'; 2 | 3 | const initialState = []; 4 | 5 | export default function rooms(state = initialState, action) { 6 | switch(action.type) { 7 | case 'all-rooms': 8 | return action.payload; 9 | case 'reconnect': 10 | return initialState; 11 | case LOGOUT_SUCCESS: 12 | return initialState; 13 | default: 14 | return state; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /server/middleware/authenticate.js: -------------------------------------------------------------------------------- 1 | var {User} = require('../models/user'); 2 | 3 | var authenticate = (req, res, next) => { 4 | var token = req.header('x-auth'); 5 | 6 | User.findByToken(token).then((user) => { 7 | if(!user) { 8 | return Promise.reject(); 9 | } 10 | 11 | req.user = user; 12 | req.token = token; 13 | next(); 14 | }).catch((e) => { 15 | res.status(401).send(); 16 | }); 17 | }; 18 | 19 | 20 | module.exports = {authenticate}; 21 | -------------------------------------------------------------------------------- /src/utils/jwtHelper.js: -------------------------------------------------------------------------------- 1 | import decode from 'jwt-decode'; 2 | 3 | export function getTokenExpirationDate(token) { 4 | const decoded = decode(token) 5 | if(!decoded.exp) { 6 | return null 7 | } 8 | 9 | const date = new Date(0) // The 0 here is the key, which sets the date to the epoch 10 | date.setUTCSeconds(decoded.exp) 11 | return date 12 | } 13 | 14 | export function isTokenExpired(token) { 15 | const date = getTokenExpirationDate(token) 16 | const offsetSeconds = 0 17 | if (date === null) { 18 | return false 19 | } 20 | return !(date.valueOf() > (new Date().valueOf() + (offsetSeconds * 1000))) 21 | } 22 | -------------------------------------------------------------------------------- /server/sockets/sockets.ts: -------------------------------------------------------------------------------- 1 | const Notifications = require('react-notification-system-redux'); 2 | const {mapObject} = require('../utils/utils'); 3 | const Elo = require('elo-js'); 4 | import Connection from './Connection'; 5 | import Player from '../game_models/players/Player'; 6 | import Room from '../game_models/rooms/Room'; 7 | 8 | 9 | module.exports.socketServer = function(io) { 10 | let conn = new Connection(io); 11 | io.on('connection', (socket) => { 12 | require('./listeners/connection')(io, socket, conn); 13 | require('./listeners/room')(io, socket, conn); 14 | require('./listeners/matchmaking')(io, socket, conn); 15 | }); 16 | }; 17 | -------------------------------------------------------------------------------- /server/game_models/ratings/Ratings.ts: -------------------------------------------------------------------------------- 1 | export default class Ratings { 2 | constructor( 3 | private _classical: number, 4 | private _rapid: number, 5 | private _blitz: number, 6 | private _bullet: number 7 | ) { } 8 | 9 | get classical() { return this._classical; } 10 | get rapid() { return this._rapid; } 11 | get blitz() { return this._blitz; } 12 | get bullet () { return this._bullet; } 13 | 14 | set classical(classical: number) { this._classical = classical; } 15 | set rapid(rapid: number) { this._rapid = rapid; } 16 | set blitz(blitz: number) { this._blitz = blitz; } 17 | set bullet (bullet: number) { this._bullet = bullet; } 18 | 19 | } -------------------------------------------------------------------------------- /src/components/auth/login.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import AuthCard from '../../containers/auth_card'; 3 | import { login, logoutUser } from '../../actions/index' 4 | import {Grid, Row, Col, Clearfix} from 'react-bootstrap'; 5 | 6 | export default class Login extends Component { 7 | render() { 8 | 9 | return ( 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /public/style/chessboard-0.3.0.min.css: -------------------------------------------------------------------------------- 1 | /*! chessboard.js v0.3.0 | (c) 2013 Chris Oakman | MIT License chessboardjs.com/license */ 2 | .clearfix-7da63{clear:both}.board-b72b1{border:2px solid #404040;-moz-box-sizing:content-box;box-sizing:content-box}.square-55d63{float:left;position:relative;-webkit-touch-callout:none;-webkit-user-select:none;-khtml-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.white-1e1d7{background-color:#f0d9b5;color:#b58863}.black-3c85d{background-color:#b58863;color:#f0d9b5}.highlight1-32417,.highlight2-9c5d2{-webkit-box-shadow:inset 0 0 3px 3px yellow;-moz-box-shadow:inset 0 0 3px 3px yellow;box-shadow:inset 0 0 3px 3px yellow}.notation-322f9{cursor:default;font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;position:absolute}.alpha-d2270{bottom:1px;right:3px}.numeric-fc462{top:2px;left:2px} -------------------------------------------------------------------------------- /server/game_models/matchmaking/QueueItem.ts: -------------------------------------------------------------------------------- 1 | import Player from '../players/Player'; 2 | 3 | export default class QueueItem { 4 | 5 | constructor( 6 | private _player: Player, 7 | private _gameType: string, 8 | private _timeControl: number, 9 | private _timeIncrement: number){} 10 | 11 | print() { 12 | return { 13 | player: this._player.username, 14 | gameType: this._gameType, 15 | timeControl: this._timeControl, 16 | timeIncrement: this._timeIncrement, 17 | } 18 | } 19 | 20 | get player(): Player { 21 | return this._player; 22 | } 23 | 24 | get gameType(): string { 25 | return this._gameType; 26 | } 27 | 28 | get timeControl(): number { 29 | return this._timeControl; 30 | } 31 | 32 | get timeIncrement(): number { 33 | return this._timeIncrement; 34 | } 35 | 36 | } -------------------------------------------------------------------------------- /src/routes.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import {Route, IndexRoute} from 'react-router'; 3 | 4 | import App from './containers/app'; 5 | import Login from './components/auth/login'; 6 | import Live from './containers/live'; 7 | import Profile from './containers/user/profile'; 8 | import RequireAuth from './components/auth/require_auth'; 9 | import Leaderboard from './containers/user/leaderboard'; 10 | import TosPrivacy from './components/terms_of_service_privacy'; 11 | import PlayerList from './containers/user/player_list'; 12 | 13 | export default ( 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | ); 23 | -------------------------------------------------------------------------------- /server/game_models/players/AI.ts: -------------------------------------------------------------------------------- 1 | import Player from './Player'; 2 | import Ratings from '../ratings/Ratings'; 3 | 4 | export default class AI extends Player { 5 | 6 | static aiId = 1; 7 | 8 | constructor( 9 | _io: any, 10 | _username: string, 11 | playerLevel: number 12 | ) { 13 | let defaultRatings: Ratings = new Ratings(1200, 1200, 1200, 1200); 14 | 15 | super( 16 | null, 17 | _username + AI.aiId, 18 | _username, 19 | "https://openclipart.org/image/75px/svg_to_png/168755/cartoon-robot.png&disposition=attachment", 20 | false, 21 | "", 22 | defaultRatings, 23 | defaultRatings, 24 | defaultRatings, 25 | defaultRatings, 26 | defaultRatings, 27 | defaultRatings, 28 | null, 29 | false); 30 | 31 | this.playerLevel = playerLevel; 32 | AI.aiId++; 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/components/search_bar.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react' 2 | 3 | export default class SearchBar extends Component { 4 | 5 | constructor(props) { 6 | super(props); 7 | 8 | this.state = {term: ''}; 9 | } 10 | 11 | render() { 12 | return ( 13 |
14 | {this.setState({term: event.target.value}) } } 18 | className="form-control" 19 | placeholder="Search a user, game #, forum entry, etc..." /> 20 | 21 | 22 | 27 | 28 |
29 | ); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /src/components/auth/require_auth.js: -------------------------------------------------------------------------------- 1 | import React, { 2 | Component 3 | } from 'react'; 4 | import { 5 | connect 6 | } from 'react-redux'; 7 | 8 | export default function(ComposedComponent) { 9 | class Authentication extends Component { 10 | static contextTypes = { 11 | router: React.PropTypes.object 12 | } 13 | 14 | componentWillMount() { 15 | if (!this.props.authenticated) { 16 | this.context.router.push('/'); 17 | } 18 | } 19 | 20 | componentWillUpdate(nextProps) { 21 | if (!nextProps.authenticated) { 22 | this.context.router.push('/'); 23 | } 24 | } 25 | 26 | render() { 27 | return 30 | } 31 | } 32 | 33 | function mapStateToProps(state) { 34 | return { 35 | authenticated: state.auth.authenticated 36 | }; 37 | } 38 | 39 | return connect(mapStateToProps)(Authentication); 40 | } 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hellochess 2 | 3 | Not under active development anymore! 4 | 5 | Setup: 6 | Create a config directory inside of the project root. Inside that folder create a config.js file. 7 | 8 | Place similar content as below inside config.js: 9 | 10 | module.exports = { 11 | secret: 'placeRandomStringHere', 12 | facebookAuth: { 13 | clientID: '', 14 | clientSecret: '', 15 | callbackURL: '', 16 | profileFields: ['id', 'email', 'name', 'picture', 'friends'], 17 | fields: "id, email, name, picture" 18 | }, 19 | googleAuth: { 20 | GoogleClientID: '', 21 | GoogleClientSecret: '' 22 | } 23 | } 24 | 25 | npm install 26 | 27 | 28 | Make sure you have MongoDB installed and run 29 | mongod 30 | 31 | For development purposes I run the webpack dev server on localhost:8080 32 | to run the webpack dev server: 33 | 34 | npm start 35 | 36 | The backend runs on localhost:3000 which I run as below: 37 | 38 | nodemon server/server.js 39 | 40 | visit localhost:8080 and hope everything works! 41 | -------------------------------------------------------------------------------- /LICENSE.MD: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 John Flickinger 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /test/test_helper.js: -------------------------------------------------------------------------------- 1 | import _$ from 'jquery'; 2 | import React from 'react'; 3 | import ReactDOM from 'react-dom'; 4 | import TestUtils from 'react-addons-test-utils'; 5 | import jsdom from 'jsdom'; 6 | import chai, { expect } from 'chai'; 7 | import chaiJquery from 'chai-jquery'; 8 | import { Provider } from 'react-redux'; 9 | import { createStore } from 'redux'; 10 | import reducers from '../src/reducers'; 11 | 12 | global.document = jsdom.jsdom(''); 13 | global.window = global.document.defaultView; 14 | global.navigator = global.window.navigator; 15 | const $ = _$(window); 16 | 17 | chaiJquery(chai, chai.util, $); 18 | 19 | function renderComponent(ComponentClass, props = {}, state = {}) { 20 | const componentInstance = TestUtils.renderIntoDocument( 21 | 22 | 23 | 24 | ); 25 | 26 | return $(ReactDOM.findDOMNode(componentInstance)); 27 | } 28 | 29 | $.fn.simulate = function(eventName, value) { 30 | if (value) { 31 | this.val(value); 32 | } 33 | TestUtils.Simulate[eventName](this[0]); 34 | }; 35 | 36 | export {renderComponent, expect}; 37 | -------------------------------------------------------------------------------- /server/models/schess.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var SChessSchema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | pgn: { 28 | type: String, 29 | }, 30 | final_fen: { 31 | type: String, 32 | rquired: true 33 | }, 34 | time: { 35 | value: { 36 | type: Number, 37 | required: true 38 | }, 39 | increment: { 40 | type: Number, 41 | required: true 42 | } 43 | }, 44 | result: { 45 | type: String, 46 | required: true 47 | } 48 | }); 49 | 50 | var SChessDB = mongoose.model('SChessGame', SChessSchema); 51 | 52 | module.exports = {SChessDB}; 53 | -------------------------------------------------------------------------------- /webpack.config.js.template: -------------------------------------------------------------------------------- 1 | const path = require('path'); 2 | const webpack = require('webpack'); 3 | const dotenv = require('dotenv').load(); 4 | const env = process.env.NODE_ENV || "development"; 5 | require('babel-polyfill'); 6 | 7 | module.exports = { 8 | entry: [ 9 | 'babel-polyfill', './src/index.jsx' 10 | ], 11 | output: { 12 | path: path.join(__dirname, 'public'), 13 | filename: './bundle.js' 14 | }, 15 | module: { 16 | loaders: [{ 17 | exclude: /(node_modules|bower_components|config)/, 18 | loader: 'babel', 19 | query: { 20 | presets: ['react', 'es2015', 'stage-1'] 21 | }, 22 | test: /\.jsx?$/ 23 | }, 24 | { 25 | test:/\.css$/, 26 | loader: 'style-loader!css-loader' 27 | }] 28 | }, 29 | resolve: { 30 | extensions: ['', '.js', '.jsx'] 31 | }, 32 | devServer: { 33 | historyApiFallback: true, 34 | contentBase: './public', 35 | inline: true 36 | }, 37 | plugins: [ 38 | new webpack.DefinePlugin({ 39 | "process.env": { 40 | NODE_ENV: JSON.stringify(env) 41 | } 42 | }) 43 | ] 44 | }; -------------------------------------------------------------------------------- /server/models/standard.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var StandardSchema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | pgn: { 28 | type: String, 29 | }, 30 | final_fen: { 31 | type: String, 32 | rquired: true 33 | }, 34 | time: { 35 | value: { 36 | type: Number, 37 | required: true 38 | }, 39 | increment: { 40 | type: Number, 41 | required: true 42 | } 43 | }, 44 | result: { 45 | type: String, 46 | required: true 47 | } 48 | }); 49 | 50 | var StandardGame = mongoose.model('StandardGame', StandardSchema); 51 | 52 | module.exports = {StandardGame}; 53 | -------------------------------------------------------------------------------- /server/models/crazyhouse.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var CrazyhouseSchema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | pgn: { 28 | type: String, 29 | }, 30 | final_fen: { 31 | type: String, 32 | rquired: true 33 | }, 34 | time: { 35 | value: { 36 | type: Number, 37 | required: true 38 | }, 39 | increment: { 40 | type: Number, 41 | required: true 42 | } 43 | }, 44 | result: { 45 | type: String, 46 | required: true 47 | } 48 | }); 49 | 50 | var CrazyhouseGame = mongoose.model('CrazyhouseGame', CrazyhouseSchema); 51 | 52 | module.exports = {CrazyhouseGame}; 53 | -------------------------------------------------------------------------------- /src/components/room/room_user_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class RoomUserList extends Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | renderUserListItem(user) { 9 | return ( 10 |
  • 13 | {user.username} 14 |
  • 15 | ); 16 | } 17 | 18 | 19 | render() { 20 | return ( 21 |
    22 |
      23 | {this.props.users 24 | .filter(user => user.anonymous !== true) 25 | .map(this.renderUserListItem)} 26 |
    27 |
    33 | Anonymous:  34 | {this.props.users 35 | .filter(user => user.anonymous === true) 36 | .length} 37 |
    38 |
    39 | ) 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /server/models/fullhouse-chess.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var FullhouseChessSchema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | pgn: { 28 | type: String, 29 | }, 30 | final_fen: { 31 | type: String, 32 | rquired: true 33 | }, 34 | time: { 35 | value: { 36 | type: Number, 37 | required: true 38 | }, 39 | increment: { 40 | type: Number, 41 | required: true 42 | } 43 | }, 44 | result: { 45 | type: String, 46 | required: true 47 | } 48 | }); 49 | 50 | var FullhouseChessDB = mongoose.model('FullhouseChessDB', FullhouseChessSchema); 51 | 52 | module.exports = {FullhouseChessDB}; 53 | -------------------------------------------------------------------------------- /src/containers/app.js: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {showError} from '../actions/index' 4 | 5 | import Notifications from 'react-notification-system-redux'; 6 | 7 | class App extends Component { 8 | 9 | render() { 10 | const {notifications} = this.props; 11 | 12 | const style = { 13 | NotificationItem: { // Override the notification item 14 | DefaultStyle: { // Applied to every notification, regardless of the notification level 15 | margin: '10px 5px 2px 1px' 16 | }, 17 | 18 | success: { // Applied only to the success notification item 19 | color: 'red' 20 | } 21 | } 22 | }; 23 | 24 | return ( 25 |
    26 | 30 | {this.props.children} 31 |
    32 | ); 33 | } 34 | } 35 | 36 | 37 | function mapStateToProps(state) { 38 | return { 39 | notifications: state.notifications 40 | } 41 | } 42 | 43 | export default connect(mapStateToProps, {showError})(App) 44 | -------------------------------------------------------------------------------- /server/models/crazyhouse960.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var Crazyhouse960Schema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | pgn: { 28 | type: String, 29 | }, 30 | final_fen: { 31 | type: String, 32 | rquired: true 33 | }, 34 | initial_fen: { 35 | type: String, 36 | required: true 37 | }, 38 | time: { 39 | value: { 40 | type: Number, 41 | required: true 42 | }, 43 | increment: { 44 | type: Number, 45 | required: true 46 | } 47 | }, 48 | result: { 49 | type: String, 50 | required: true 51 | } 52 | }); 53 | 54 | var Crazyhouse960Game = mongoose.model('Crazyhouse960Game', Crazyhouse960Schema); 55 | 56 | module.exports = {Crazyhouse960Game}; 57 | -------------------------------------------------------------------------------- /src/actions/types.js: -------------------------------------------------------------------------------- 1 | import {production, staging, local} from '../../config/config'; 2 | 3 | let url; 4 | if (process.env.NODE_ENV === "production") { 5 | url = production; 6 | } else if (process.env.NODE_ENV === "staging") { 7 | url = staging; 8 | } else { 9 | url = local; 10 | } 11 | 12 | export const ROOT_URL = url; 13 | export const LOGIN_SUCCESS = 'LOGIN_SUCCESS'; 14 | export const LOGIN_ERROR = 'LOGIN_ERROR'; 15 | export const LOGOUT_SUCCESS = 'LOGOUT_SUCCESS'; 16 | export const SELECTED_ROOM = 'SELECTED_ROOM'; 17 | export const UPDATE_USERNAME = 'UPDATE_USERNAME'; 18 | export const SELECTED_GAME_TYPE= 'SELECTED_GAME_TYPE'; 19 | export const SELECTED_TIME = 'SELECTED_TIME'; 20 | export const SELECTED_TIME_INCREMENT = 'SELECTED_TIME_INTERVAL'; 21 | export const CREATE_GAME_ROOM = 'CREATE_GAME_ROOM'; 22 | export const GAME_ROOM_NAME = 'GAME_ROOM_NAME'; 23 | export const ENABLE_VOICE_CHAT = 'ENABLE_VOICE_CHAT'; 24 | export const RESET_NEW_GAME_MODAL = 'RESET_NEW_GAME_MODAL'; 25 | export const SET_MAX_PLAYERS = 'SET_MAX_PLAYERS'; 26 | export const TOGGLE_PRIVATE = 'TOGGLE_PRIVATE'; 27 | export const VIEW_PROFILE = 'VIEW_PROFILE'; 28 | export const VIEW_LEADERBOARD = 'VIEW_LEADERBOARD'; 29 | export const RECENT_GAMES = 'RECENT_GAMES'; 30 | export const CLOSE_ANALYSIS = 'CLOSE_ANALYSIS'; 31 | export const PLAYERLIST_SUCCESS = 'PLAYERLIST_SUCCESS'; 32 | export const PLAYERLIST_DONE = 'PLAYERLIST_DONE'; -------------------------------------------------------------------------------- /src/components/auth/login_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { Field, reduxForm } from 'redux-form'; 3 | import {FormGroup, FormControl, ControlLabel, Button} from 'react-bootstrap'; 4 | 5 | class LoginForm extends Component { 6 | 7 | render() { 8 | const { handleSubmit } = this.props; 9 | return ( 10 |
    12 | 13 | Email 14 | 20 | 21 | 22 | Password 23 | 29 | 30 | 36 |
    37 | ); 38 | } 39 | } 40 | 41 | LoginForm = reduxForm({ 42 | form: 'loginForm', 43 | })(LoginForm) 44 | 45 | export default LoginForm; 46 | -------------------------------------------------------------------------------- /src/containers/pgn/pgn_buttons.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | import PGNFirstButton from './pgn_first_button'; 5 | import PGNPrevButton from './pgn_prev_button'; 6 | import PGNNextButton from './pgn_next_button'; 7 | import PGNLastButton from './pgn_last_button'; 8 | 9 | class PGNButtons extends Component { 10 | 11 | constructor(props) { 12 | super(props); 13 | }; 14 | 15 | shouldComponentUpdate(nextProps) { 16 | return false; 17 | } 18 | 19 | componentWillReceiveProps(nextProps) { 20 | 21 | } 22 | 23 | componentWillMount() { 24 | } 25 | 26 | render() { 27 | let roomName = this.props.room.room.name; 28 | return ( 29 |
    30 | 31 | 32 | 33 | 34 |
    35 | ); 36 | } 37 | } 38 | 39 | function mapStateToProps(state, props) { 40 | let activeThread = state.activeThread; 41 | let room = state.openThreads[activeThread]; 42 | let game = room.game; 43 | let pgn = game.pgn; 44 | let activePly = room.activePly; 45 | return { 46 | activeThread: activeThread, 47 | room: room, 48 | game: game, 49 | pgn: pgn, 50 | activePly: activePly 51 | } 52 | } 53 | 54 | function mapDispatchToProps(dispatch) { 55 | return { 56 | changeActivePly: (roomName, ply) => { 57 | dispatch(changeActivePly(roomName, ply)) 58 | } 59 | } 60 | } 61 | 62 | export default connect(mapStateToProps, mapDispatchToProps)(PGNButtons); 63 | -------------------------------------------------------------------------------- /src/index.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import {Provider} from 'react-redux'; 4 | import {createStore, applyMiddleware, compose} from 'redux'; 5 | import {Router, browserHistory} from 'react-router'; 6 | import ReduxPromise from 'redux-promise'; 7 | import thunkMiddleware from 'redux-thunk' 8 | import Notifications from 'react-notification-system-redux'; 9 | 10 | import {socketIoMiddleware} from './middleware/socketio'; 11 | import startSocketListeners, {socket} from './middleware/socketio'; 12 | import {voiceMiddleware} from './middleware/voice'; 13 | import startVoiceListeners from './middleware/voice'; 14 | import reducers from './reducers'; 15 | import routes from './routes'; 16 | import {LOGIN_SUCCESS} from './actions/types'; 17 | 18 | const middleware = [ReduxPromise, thunkMiddleware, voiceMiddleware, socketIoMiddleware]; 19 | const composeEnhancers = 20 | typeof window === 'object' && 21 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ ? 22 | window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({ 23 | actionsBlacklist: ['update-time'] 24 | }) : compose; 25 | const store = createStore(reducers, composeEnhancers( 26 | applyMiddleware(...middleware) 27 | )); 28 | startSocketListeners(store); 29 | startVoiceListeners(store); 30 | 31 | //Get the authentication token and user profile if available 32 | const token = localStorage.getItem('token'); 33 | const profile = localStorage.getItem('profile'); 34 | 35 | //The user has both a token and profile, consider them logged in 36 | if(token && profile) { 37 | store.dispatch({ 38 | type: LOGIN_SUCCESS 39 | }); 40 | } 41 | ReactDOM.render( 42 | 43 | 44 | , document.querySelector('.container-fluid')); 45 | -------------------------------------------------------------------------------- /src/reducers/new_game_reducer.js: -------------------------------------------------------------------------------- 1 | import * as ActionTypes from '../actions/types'; 2 | 3 | const initialState = { 4 | gameType: "four-player", 5 | time: { 6 | value: 10, 7 | increment: 3 8 | }, 9 | isMakingGameRoom: false, 10 | room: { 11 | voiceChat: false, 12 | maxPlayers: 50, 13 | private: false, 14 | roomMode: 'open-table' 15 | } 16 | } 17 | 18 | export default function newGameOptions (state=initialState, action) { 19 | switch(action.type) { 20 | case ActionTypes.SELECTED_GAME_TYPE: 21 | return {...state, gameType: action.payload} 22 | case ActionTypes.SELECTED_TIME: 23 | return {...state, time: { ...state.time, value: action.payload } }; 24 | case ActionTypes.SELECTED_TIME_INCREMENT: 25 | return {...state, time: { ...state.time, increment: action.payload } }; 26 | case ActionTypes.CREATE_GAME_ROOM: 27 | return {...state, isMakingGameRoom: true}; 28 | case ActionTypes.GAME_ROOM_NAME: 29 | return {...state, room: { ...state.room, name: action.payload}} 30 | case ActionTypes.ENABLE_VOICE_CHAT: { 31 | return {...state, room: { ...state.room, voiceChat: !state.room.voiceChat}} 32 | } 33 | case ActionTypes.RESET_NEW_GAME_MODAL: 34 | return initialState; 35 | case ActionTypes.SET_MAX_PLAYERS: 36 | return {...state, room: {...state.room, maxPlayers: action.payload}} 37 | case ActionTypes.TOGGLE_PRIVATE: 38 | return {...state, room: {...state.room, private: !state.room.private}} 39 | case 'selected-room-mode': 40 | return {...state, room: {...state.room, roomMode: action.payload}}; 41 | default: 42 | return state; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/components/chat_message/message_list.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import MessageListItem from './message_list_item'; 4 | import {mapObject} from '../../utils' 5 | import {ListGroup} from 'react-bootstrap'; 6 | 7 | export default class MessageList extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | } 12 | 13 | scrollToBottom() { 14 | this.refs.msgList.scrollTop = 99999; 15 | } 16 | 17 | componentDidUpdate(prevProps) { 18 | function lastMessageTime(props) { 19 | return (new Date(_.last(props.messages).time)).getTime(); 20 | } 21 | if (prevProps.messages.length !== this.props.messages.length || 22 | prevProps.thread != this.props.thread || 23 | lastMessageTime(prevProps) !== lastMessageTime(this.props)) { 24 | this.scrollToBottom(); 25 | } 26 | } 27 | 28 | componentDidMount() { 29 | this.scrollToBottom(); 30 | } 31 | 32 | renderChatListItem(index, message) { 33 | return ( 34 | 44 | ); 45 | } 46 | 47 | render() { 48 | const { messages } = this.props; 49 | 50 | return ( 51 |
    52 | 53 | {mapObject(messages, this.renderChatListItem)} 54 | 55 |
    56 | ); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/components/room/browser_notification.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Notification from 'react-web-notification'; 3 | 4 | export default class BrowserNotification extends Component { 5 | constructor(props) { 6 | super(props); 7 | this.title = this.props.title || "HelloChess: It's your move."; 8 | this.body = this.props.body || "It's your move on HelloChess!"; 9 | this.options = { 10 | body: this.body 11 | }; 12 | this.ignore = true; 13 | } 14 | 15 | shouldComponentUpdate(nextProps, nextState) { 16 | return true; 17 | } 18 | 19 | componentWillReceiveProps(nextProps) { 20 | } 21 | 22 | componentDidUpdate(prevProps, prevState) { 23 | } 24 | 25 | handlePermissionGranted() { 26 | 27 | } 28 | 29 | handlePermissionDenied() { 30 | 31 | } 32 | 33 | handleNotSupported() { 34 | 35 | } 36 | 37 | handleOnClick() { 38 | window.focus(); 39 | } 40 | 41 | handleOnError() { 42 | 43 | } 44 | 45 | handleOnClose() { 46 | 47 | } 48 | 49 | handleOnShow() { 50 | } 51 | 52 | render() { 53 | return ( 54 | 67 | ); 68 | } 69 | 70 | } -------------------------------------------------------------------------------- /src/components/room/room_settings.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | 3 | export default class RoomSettings extends Component { 4 | constructor(props) { 5 | super(props); 6 | } 7 | 8 | renderSettingsModal(value) { 9 | 10 | return ( 11 | 30 | ); 31 | } 32 | 33 | render() { 34 | const {value} = this.props; 35 | if(value.host) { 36 | return ( 37 |
    38 | 42 | Settings 43 | 44 | {this.renderSettingsModal(value)} 45 |
    46 | ); 47 | } 48 | 49 | return null; 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/middleware/voice.js: -------------------------------------------------------------------------------- 1 | let webrtc = null, voice = false; 2 | 3 | export function voiceMiddleware(store) { 4 | 5 | return next => action => { 6 | if(!webrtc && voice) { 7 | return; 8 | } 9 | const result = next(action); 10 | 11 | switch(action.type) { 12 | case 'voice/enable-voice': 13 | let profile = store.getState().auth.profile; 14 | webrtc.leaveRoom(action.payload); 15 | webrtc.joinRoom(action.payload); 16 | webrtc.startLocalVideo(); 17 | return action; 18 | 19 | case 'voice/disable-voice': 20 | // webrtc.leaveRoom(action.payload) 21 | webrtc.stopLocalVideo(); 22 | return action; 23 | case 'server/join-room': 24 | if(action.payload.room.voiceChat == true) { 25 | console.log("joining voice room: ", action.payload.room.name); 26 | webrtc.leaveRoom(action.payload.room.name); 27 | webrtc.joinRoom(action.payload.room.name); 28 | return action; 29 | } 30 | case 'server/leave-room': 31 | webrtc.leaveRoom(action.payload); 32 | webrtc.stopLocalVideo(); 33 | return action; 34 | } 35 | 36 | return result; 37 | } 38 | } 39 | 40 | export default function(store) { 41 | webrtc = new SimpleWebRTC({ 42 | // the id/element dom element that will hold "our" video 43 | localVideoEl: 'video', 44 | // the id/element dom element that will hold remote videos 45 | remoteVideosEl: 'remotesVideos', 46 | // wait for permission 47 | autoRequestMedia: false, 48 | media: {video: false, audio: true} 49 | }); 50 | 51 | // we have to wait until it's ready 52 | webrtc.on('readyToCall', function () { 53 | // you can name it anything 54 | voice = true; 55 | }); 56 | } -------------------------------------------------------------------------------- /src/containers/pgn/pgn_move.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | 5 | class PGNMove extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | shouldComponentUpdate(nextProps) { 11 | if (this.props.activePly != nextProps.activePly) { 12 | return true; 13 | } 14 | if (this.props.pgn.length != nextProps.pgn.length) { 15 | return true; 16 | } 17 | return false; 18 | } 19 | 20 | selectMove() { 21 | this.props.changeActivePly(this.props.room.room.name, this.props.move.ply); 22 | } 23 | 24 | colorClass() { 25 | if (typeof this.props.move.color !== "undefined") { 26 | return " " + this.props.move.color; 27 | } 28 | return ""; 29 | } 30 | 31 | render() { 32 | let className = "pgn-move" + this.colorClass(); 33 | if (this.props.move.ply === this.props.activePly) { 34 | className += " active"; 35 | } 36 | return ( 37 |
    40 | {this.props.move.san} 41 |
    42 | ); 43 | } 44 | 45 | } 46 | 47 | function mapStateToProps(state, props) { 48 | let activeThread = state.activeThread; 49 | let room = state.openThreads[activeThread]; 50 | let selectedMove = room.selectedMove; 51 | let game = room.game; 52 | let pgn = game.pgn; 53 | let activePly = room.activePly; 54 | return { 55 | activeThread: activeThread, 56 | room: room, 57 | selectedMove: selectedMove, 58 | game: game, 59 | pgn: pgn, 60 | activePly: activePly 61 | } 62 | } 63 | 64 | function mapDispatchToProps(dispatch) { 65 | return { 66 | changeActivePly: (roomName, ply) => { 67 | dispatch(changeActivePly(roomName, ply)) 68 | } 69 | } 70 | } 71 | 72 | export default connect(mapStateToProps, mapDispatchToProps)(PGNMove); -------------------------------------------------------------------------------- /src/reducers/profile_reducer.js: -------------------------------------------------------------------------------- 1 | import {VIEW_PROFILE, PLAYERLIST_SUCCESS, VIEW_LEADERBOARD, RECENT_GAMES, PLAYERLIST_DONE} from '../actions/types'; 2 | 3 | export default function currentProfile(state = {}, action) { 4 | switch(action.type) { 5 | case VIEW_PROFILE: 6 | return action.payload; 7 | case 'CLEAR_CURRENT_PROFILE': 8 | return {}; 9 | default: 10 | return state; 11 | } 12 | } 13 | 14 | export function playerList(state = { 15 | players: [], 16 | hasMore: true, 17 | }, action) { 18 | let newState = null; 19 | switch(action.type) { 20 | case PLAYERLIST_SUCCESS: 21 | newState = Object.assign({}, state); 22 | newState.players = newState.players.concat(action.payload); 23 | return newState; 24 | case PLAYERLIST_DONE: 25 | newState = Object.assign({}, state); 26 | newState.hasMore = false; 27 | return newState; 28 | default: 29 | return state; 30 | } 31 | } 32 | 33 | export function leaderboard(state = {}, action) { 34 | switch(action.type) { 35 | case VIEW_LEADERBOARD: 36 | return action.payload; 37 | default: 38 | return state; 39 | } 40 | } 41 | 42 | export function recentGames(state = [], action) { 43 | 44 | switch (action.type) { 45 | case RECENT_GAMES: 46 | return action.payload; 47 | case 'CLEAR_RECENT_GAMES': 48 | return []; 49 | default: 50 | return state; 51 | } 52 | } 53 | 54 | export function matchmaking(state = { 55 | searching: false, 56 | }, action) { 57 | switch(action.type) { 58 | case 'in-queue': 59 | return { 60 | searching: true, 61 | timeControl: action.payload.timeControl, 62 | increment: action.payload.increment, 63 | gameType: action.payload.gameType, 64 | }; 65 | case 'stopped-pairing': 66 | let newState = Object.assign({}, state); 67 | newState.searching = false; 68 | return newState; 69 | default: 70 | return state; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/reducers/auth_reducer.js: -------------------------------------------------------------------------------- 1 | import {isTokenExpired} from '../utils/jwtHelper'; 2 | import * as ActionTypes from '../actions/types' 3 | 4 | function getProfile() { 5 | if(typeof(Storage) === "undefined") { 6 | return {}; 7 | } 8 | return JSON.parse(localStorage.getItem('profile')); 9 | } 10 | 11 | function setProfile(profile) { 12 | if(typeof(Storage) === "undefined") { 13 | return {}; 14 | } 15 | return localStorage.setItem('profile', JSON.stringify(profile)); 16 | } 17 | 18 | //SETUP initial app state 19 | let authenticated = false; 20 | let token = null; 21 | if(typeof(Storage) !== "undefined") { 22 | token = localStorage.getItem('id_token'); 23 | } 24 | 25 | if (token && !isTokenExpired(token)) { 26 | authenticated = true; 27 | } 28 | const INITIAL_STATE = { 29 | profile: getProfile(), 30 | isAuthenticated: authenticated, 31 | voice: false 32 | } 33 | 34 | //The auth reducer 35 | function auth(state = INITIAL_STATE, action) { 36 | let newState = null; 37 | switch (action.type) { 38 | case ActionTypes.LOGIN_SUCCESS: 39 | return {...state, authenticated: true, profile: getProfile()} 40 | case ActionTypes.LOGIN_ERROR: 41 | return {...state, authenticated: false}; 42 | case ActionTypes.LOGOUT_SUCCESS: 43 | return Object.assign({}, state, { 44 | authenticated: false, 45 | profile: null 46 | }); 47 | case ActionTypes.UPDATE_USERNAME: 48 | setProfile(action.payload.data); 49 | newState = Object.assign({}, state) 50 | newState.profile = getProfile(); 51 | return newState; 52 | case 'update-user': 53 | setProfile(action.payload); 54 | newState = Object.assign({}, state) 55 | newState.profile = getProfile(); 56 | return newState; 57 | case 'reconnect': 58 | if(typeof(Storage) !== "undefined") { 59 | localStorage.removeItem('token'); 60 | localStorage.removeItem('profile'); 61 | } 62 | return INITIAL_STATE; 63 | default: 64 | return state 65 | } 66 | } 67 | export default auth; 68 | -------------------------------------------------------------------------------- /server/models/fourgame.js: -------------------------------------------------------------------------------- 1 | const mongoose = require('mongoose'); 2 | const Schema = mongoose.Schema; 3 | 4 | var FourWaySchema = new Schema({ 5 | white: { 6 | user_id: { 7 | type: mongoose.Schema.Types.ObjectId, 8 | required: true, 9 | ref: 'User' 10 | }, 11 | elo: { 12 | type: Number, 13 | required: true 14 | } 15 | }, 16 | black: { 17 | user_id: { 18 | type: mongoose.Schema.Types.ObjectId, 19 | required: true, 20 | ref: 'User' 21 | }, 22 | elo: { 23 | type: Number, 24 | required: true 25 | } 26 | }, 27 | gold: { 28 | user_id: { 29 | type: mongoose.Schema.Types.ObjectId, 30 | required: true, 31 | ref: 'User' 32 | }, 33 | elo: { 34 | type: Number, 35 | required: true 36 | } 37 | }, 38 | red: { 39 | user_id: { 40 | type: mongoose.Schema.Types.ObjectId, 41 | required: true, 42 | ref: 'User' 43 | }, 44 | elo: { 45 | type: Number, 46 | required: true 47 | } 48 | }, 49 | pgn: { 50 | type: String, 51 | required: true, 52 | }, 53 | final_fen: { 54 | type: String, 55 | rquired: true 56 | }, 57 | time: { 58 | value: { 59 | type: Number, 60 | required: true 61 | }, 62 | increment: { 63 | type: Number, 64 | required: true 65 | } 66 | }, 67 | //Loser order fourth, third, second, first. e.g. {w: 1, g: 2, b: 3, r: 4} 68 | //implying white lost first and red won 69 | loser_order: { 70 | w: { 71 | type: Number, 72 | required: true 73 | }, 74 | b: { 75 | type: Number, 76 | required: true 77 | }, 78 | g: { 79 | type: Number, 80 | required: true, 81 | }, 82 | r: { 83 | type: Number, 84 | required: true, 85 | }, 86 | } 87 | }); 88 | 89 | var FourGameDB = mongoose.model('FourWay', FourWaySchema); 90 | 91 | module.exports = {FourGameDB}; 92 | -------------------------------------------------------------------------------- /src/containers/message_send.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { bindActionCreators } from 'redux'; 4 | 5 | import {Button} from 'react-bootstrap'; 6 | 7 | //The input where the users can send messages to a chat 8 | class MessageSend extends Component { 9 | 10 | constructor(props) { 11 | super(props); 12 | this.state = { 13 | msg: '' 14 | }; 15 | 16 | this.onInputChange = this.onInputChange.bind(this); 17 | this.onMessageSend = this.onMessageSend.bind(this); 18 | } 19 | 20 | onInputChange(event) { 21 | this.setState({msg: event.target.value}); 22 | } 23 | 24 | onMessageSend(event) { 25 | const msg = this.state.msg; 26 | if(msg.length < 1) { 27 | event.preventDefault(); 28 | return; 29 | } 30 | 31 | this.props.dispatch({ 32 | type:'server/new-message', 33 | payload: { 34 | msg: msg, 35 | thread: this.props.activeThread, 36 | } 37 | }); 38 | this.setState({msg: ''}); 39 | event.preventDefault(); 40 | } 41 | 42 | render() { 43 | return ( 44 |
    45 |
    46 | 53 | 54 | 58 | 59 |
    60 |
    61 | ); 62 | } 63 | } 64 | 65 | function mapStoreToProps(store) { 66 | return { 67 | messages: store.messages, 68 | profile: store.auth.profile, 69 | activeThread: store.activeThread 70 | }; 71 | } 72 | 73 | export default connect(mapStoreToProps)(MessageSend); 74 | -------------------------------------------------------------------------------- /server/router.js: -------------------------------------------------------------------------------- 1 | const Authentication = require('./controllers/authentication'); 2 | const User = require('./controllers/user'); 3 | const Games = require('./controllers/games'); 4 | const passportService = require('./services/passport'); 5 | const passport = require('passport'); 6 | const path = require('path'); 7 | 8 | const requireAuth = passport.authenticate('jwt', {session: false}); 9 | const requireLogin = passport.authenticate('local', {session: false}); 10 | 11 | const requireFB = passport.authenticate('facebook-token'); 12 | const requireGoogle = passport.authenticate('google-plus-token'); 13 | 14 | module.exports = function(app) { 15 | 16 | app.use(passport.initialize()); 17 | app.use(passport.session()); 18 | 19 | app.get("/live", (req, res) => { 20 | res.sendFile(path.join(__dirname, "../public/index.html")); 21 | }); 22 | 23 | app.get("/players", (req, res) => { 24 | res.sendFile(path.join(__dirname, "../public/index.html")); 25 | }); 26 | 27 | app.get("/leaderboard", (req, res) => { 28 | res.sendFile(path.join(__dirname, "../public/index.html")); 29 | }); 30 | 31 | app.get('/profile/:id', (req, res) => { 32 | res.sendFile(path.join(__dirname, "../public/index.html")); 33 | }); 34 | 35 | app.get("/robots.txt", (req, res) => { 36 | res.sendFile(path.join(__dirname, "../public/robots.txt")); 37 | }); 38 | 39 | app.get("/tosandprivacy", (req, res) => { 40 | res.sendFile(path.join(__dirname, "../public/index.html")); 41 | }); 42 | 43 | //Authentication routes 44 | app.post('/api/users/signup', Authentication.signup); 45 | app.post('/api/users/login', requireLogin, Authentication.login); 46 | app.post('/api/users/anonLogin', Authentication.anonLogin); 47 | app.post('/api/auth/facebook/token', requireFB, Authentication.fbLogin); 48 | app.get('/api/auth/google/token', requireGoogle, Authentication.googleLogin); 49 | app.patch('/api/users/:id', requireAuth, User.updateUser); 50 | app.get('/api/users/:id', requireAuth, User.getUserProfile); 51 | app.get('/api/leaderboard', User.getLeaderboard); 52 | app.get('/api/playerlist/:n/', User.getPlayers); 53 | app.get('/api/games/recentgames/:id', requireAuth, Games.getRecentGames); 54 | app.get('/api/users/search/:q', User.userSearch); 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/containers/pgn/pgn_first_button.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | import {Button} from 'react-bootstrap'; 5 | 6 | class PGNFirstButton extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | componentDidMount() { 11 | this.bindArrowKey(); 12 | } 13 | componentDidUpdate() { 14 | this.bindArrowKey(); 15 | } 16 | componentWillUnmount() { 17 | $(document).off('keydown'); 18 | } 19 | bindArrowKey() { 20 | $(document).on('keydown', this.onArrowKey.bind(this)); 21 | } 22 | onArrowKey(event) { 23 | let keyCode = '38'; // up arrow 24 | let tag = event.target.tagName.toLowerCase(); 25 | if (event.which == keyCode && tag !== 'input') { 26 | this.onClick(); 27 | event.preventDefault(); 28 | event.stopImmediatePropagation(); 29 | return false; 30 | } 31 | } 32 | onClick() { 33 | let roomName = this.props.room.room.name; 34 | this.props.changeActivePly(roomName, 0); 35 | } 36 | disabled() { 37 | return ( 38 | typeof this.props.activePly === "undefined" || 39 | this.props.activePly === 0 40 | ); 41 | } 42 | render() { 43 | return ( 44 | 51 | ); 52 | } 53 | } 54 | 55 | function mapStateToProps(state, props) { 56 | let activeThread = state.activeThread; 57 | let room = state.openThreads[activeThread]; 58 | let game = room.game; 59 | let pgn = game.pgn; 60 | let activePly = room.activePly; 61 | return { 62 | activeThread: activeThread, 63 | room: room, 64 | game: game, 65 | pgn: pgn, 66 | activePly: activePly 67 | } 68 | } 69 | 70 | function mapDispatchToProps(dispatch) { 71 | return { 72 | changeActivePly: (roomName, ply) => { 73 | dispatch(changeActivePly(roomName, ply)) 74 | } 75 | } 76 | } 77 | 78 | export default connect(mapStateToProps, mapDispatchToProps)(PGNFirstButton); -------------------------------------------------------------------------------- /server/controllers/authentication.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {User} = require('../models/user'); 3 | const {ObjectID} = require('mongodb'); 4 | 5 | exports.signup = (req, res, next) => { 6 | 7 | const body = _.pick(req.body, [ 8 | 'email', 9 | 'password' 10 | ]); 11 | 12 | if(!body.email) { 13 | return res.status(400).send("Email is required"); 14 | } 15 | 16 | if(!body.password) { 17 | return res.status(400).send("Password is required"); 18 | } 19 | 20 | const user = new User(body); 21 | 22 | //User does NOT exist, save it to DB 23 | user.save().then(() => { 24 | return user.generateAuthToken(); 25 | }).then((token) => { 26 | res.header('x-auth', token).send(user); 27 | }).catch((e) => { 28 | if(e.code === 11000) { 29 | res.status(422).send("Email already in use!"); 30 | } 31 | res.status(400); 32 | }) 33 | } 34 | 35 | exports.login = (req, res, next) => { 36 | req.user.generateAuthToken().then((token) => { 37 | res.header('x-auth', token).send(req.user); 38 | }).catch((e) => { 39 | res.status(401).send(); 40 | }); 41 | } 42 | 43 | exports.anonLogin = (req, res, next) => { 44 | let defaultRatings = { 45 | classic: 1200, rapid: 1200, blitz: 1200, bullet: 1200 46 | } 47 | let newID = ObjectID(); 48 | let anonProfile = { 49 | _id: newID, 50 | picture: "/img/default-img.png", 51 | email: "", 52 | username: "Anonymous", 53 | social: false, 54 | anonymous: true, 55 | standard_ratings: {...defaultRatings}, 56 | schess_ratings: {...defaultRatings}, 57 | fourplayer_ratings: {...defaultRatings}, 58 | crazyhouse_ratings: {...defaultRatings}, 59 | crazyhouse960_ratings: {...defaultRatings}, 60 | fullhouse_ratings: {...defaultRatings} 61 | }; 62 | res.send(anonProfile); 63 | } 64 | 65 | exports.fbLogin = (req, res) => { 66 | req.user.generateAuthToken().then((token) => { 67 | res.header('x-auth', token).send(req.user); 68 | }).catch((e) => { 69 | res.status(401).send(); 70 | }) 71 | } 72 | 73 | exports.googleLogin = (req, res) => { 74 | req.user.generateAuthToken().then((token) => { 75 | res.header('x-auth', token).send(req.user); 76 | }).catch((e) => { 77 | res.status(401).send(); 78 | }) 79 | } 80 | -------------------------------------------------------------------------------- /src/actions/create_game.js: -------------------------------------------------------------------------------- 1 | import { 2 | SELECTED_GAME_TYPE, 3 | SELECTED_TIME, 4 | SELECTED_TIME_INCREMENT, 5 | CREATE_GAME_ROOM, 6 | GAME_ROOM_NAME, 7 | ENABLE_VOICE_CHAT, 8 | RESET_NEW_GAME_MODAL, 9 | SET_MAX_PLAYERS, 10 | TOGGLE_PRIVATE 11 | } from './types'; 12 | 13 | export function startPairing(gameType, timeControl, increment) { 14 | return (dispatch) => { 15 | return dispatch({ 16 | type: 'server/pair-me', 17 | payload: { 18 | timeControl, 19 | increment, 20 | gameType 21 | } 22 | }); 23 | } 24 | } 25 | 26 | export function stopPairing() { 27 | return { 28 | type: 'server/stop-pairing', 29 | } 30 | } 31 | 32 | export function selectedGameType(value) { 33 | return { 34 | type: SELECTED_GAME_TYPE, 35 | payload: value 36 | } 37 | } 38 | 39 | export function selectedNewTime(value) { 40 | return { 41 | type: SELECTED_TIME, 42 | payload: value 43 | } 44 | } 45 | 46 | export function selectedNewTimeIncrement(value) { 47 | return { 48 | type: SELECTED_TIME_INCREMENT, 49 | payload: value 50 | } 51 | } 52 | 53 | export function createGameRoom() { 54 | return { 55 | type: CREATE_GAME_ROOM 56 | } 57 | } 58 | 59 | export function enableVoiceChat() { 60 | return { 61 | type: ENABLE_VOICE_CHAT 62 | } 63 | } 64 | 65 | export function resetNewGameModal() { 66 | return { 67 | type: RESET_NEW_GAME_MODAL 68 | } 69 | } 70 | 71 | export function changeMaxPlayers(value) { 72 | return { 73 | type: SET_MAX_PLAYERS, 74 | payload: value 75 | } 76 | } 77 | 78 | export function togglePrivate() { 79 | return { 80 | type: TOGGLE_PRIVATE 81 | } 82 | } 83 | 84 | //Sends a request to create a new game room 85 | export function finalizeGameRoom(game, host) { 86 | delete game.isMakingGameRoom; 87 | delete host.email; 88 | game.host = host; 89 | return (dispatch) => { 90 | return dispatch({ 91 | type: 'server/create-room', 92 | payload: game 93 | }); 94 | } 95 | } 96 | 97 | export function selectedRoomMode(value) { 98 | return { 99 | type: 'selected-room-mode', 100 | payload: value 101 | } 102 | } -------------------------------------------------------------------------------- /src/containers/pgn/pgn_viewer.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import PGNButtons from './pgn_buttons'; 4 | import PGNMoveList from './pgn_move_list'; 5 | import {changeActivePly} from '../../actions/room'; 6 | 7 | class PGNViewer extends Component { 8 | 9 | constructor(props) { 10 | super(props); 11 | }; 12 | 13 | shouldComponentUpdate(nextProps) { 14 | if(nextProps.activeThread != this.props.activeThread) 15 | return true; 16 | if(nextProps.game.fen != this.props.game.fen) { 17 | return true; 18 | } 19 | if (nextProps.pgn != this.props.pgn) { 20 | return true; 21 | } 22 | if (nextProps.activePly != this.props.activePly) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | componentWillReceiveProps(nextProps) { 29 | 30 | } 31 | 32 | componentWillMount() { 33 | if (this.props.pgn) { 34 | 35 | } 36 | } 37 | 38 | render() { 39 | let moves = this.props.pgn; 40 | 41 | /* 42 | moves = ['e4', 'e5', 43 | 'Nf3', 'Nc6', 44 | 'Bb5', 'a6', 45 | 'Ba4', 'Nf6', 46 | 'O-O', 'O-O', 47 | 'Re1', 'b5', 48 | 'Bb3', 'd6', 49 | 'c3', 'h6', 50 | 'h3', 'c5', 51 | 'd5']; 52 | */ 53 | let numPlys = moves.length; 54 | return ( 55 |
    56 | 57 | 58 |
    59 | ); 60 | } 61 | } 62 | 63 | function mapStateToProps(state, props) { 64 | let activeThread = state.activeThread; 65 | let room = state.openThreads[activeThread]; 66 | let game = room.game; 67 | let pgn = game.pgn; 68 | let activePly = state.activePly; 69 | return { 70 | activeThread: activeThread, 71 | room: room, 72 | game: game, 73 | pgn: pgn, 74 | activePly: activePly 75 | } 76 | } 77 | 78 | function mapDispatchToProps(dispatch) { 79 | return { 80 | changeActivePly: (roomName, ply) => { 81 | dispatch(changeActivePly(roomName, ply)) 82 | } 83 | } 84 | } 85 | 86 | export default connect(mapStateToProps, mapDispatchToProps)(PGNViewer); 87 | -------------------------------------------------------------------------------- /src/containers/pgn/pgn_last_button.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | import {Button} from 'react-bootstrap'; 5 | 6 | class PGNLastButton extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | componentDidMount() { 11 | this.bindArrowKey(); 12 | } 13 | componentDidUpdate() { 14 | this.bindArrowKey(); 15 | } 16 | componentWillUnmount() { 17 | $(document).off('keydown'); 18 | } 19 | bindArrowKey() { 20 | $(document).on('keydown', this.onArrowKey.bind(this)); 21 | } 22 | onArrowKey(event) { 23 | let keyCode = '40'; // down arrow 24 | let tag = event.target.tagName.toLowerCase(); 25 | if (event.which == keyCode && tag !== 'input') { 26 | this.onClick(); 27 | event.preventDefault(); 28 | event.stopImmediatePropagation(); 29 | return false; 30 | } 31 | } 32 | onClick() { 33 | let roomName = this.props.room.room.name; 34 | let lastPly = this.props.pgn.length; 35 | this.props.changeActivePly(roomName, lastPly); 36 | } 37 | disabled() { 38 | return ( 39 | typeof this.props.activePly === "undefined" || 40 | this.props.activePly === this.props.pgn.length 41 | ); 42 | } 43 | render() { 44 | return ( 45 | 52 | ); 53 | } 54 | 55 | } 56 | function mapStateToProps(state, props) { 57 | let activeThread = state.activeThread; 58 | let room = state.openThreads[activeThread]; 59 | let game = room.game; 60 | let pgn = game.pgn; 61 | let activePly = room.activePly; 62 | return { 63 | activeThread: activeThread, 64 | room: room, 65 | game: game, 66 | pgn: pgn, 67 | activePly: activePly 68 | } 69 | } 70 | 71 | function mapDispatchToProps(dispatch) { 72 | return { 73 | changeActivePly: (roomName, ply) => { 74 | dispatch(changeActivePly(roomName, ply)) 75 | } 76 | } 77 | } 78 | 79 | export default connect(mapStateToProps, mapDispatchToProps)(PGNLastButton); -------------------------------------------------------------------------------- /server/server.ts: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const dotenv = require('dotenv').load(); 3 | const express = require('express'); 4 | const app = express(); 5 | 6 | import {production, staging, staging2, local} from '../config/config'; 7 | 8 | const env = process.env.NODE_ENV || "development"; 9 | 10 | let http = require('http').createServer(app); 11 | 12 | const path = require('path'); 13 | const bodyParser = require('body-parser'); 14 | const morgan = require('morgan'); 15 | const _ = require('lodash'); 16 | let io = require('socket.io')(http); 17 | 18 | const cors = require('cors'); 19 | 20 | const router = require('./router'); 21 | const {mongoose} = require('./db/mongoose'); 22 | const {User} = require('./models/user'); 23 | const {authenticate} = require('./middleware/authenticate'); 24 | 25 | let allowCrossDomain = null; 26 | 27 | //CORS middleware for testing purposes 28 | if(env == "production") { 29 | allowCrossDomain = function(req, res, next) { 30 | res.header('Access-Control-Allow-Origin', [ 31 | 'https://www.hellochess.com' 32 | ]); 33 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH'); 34 | res.header('Access-Control-Allow-Headers', 'Content-Type, x-auth'); 35 | res.header('Access-Control-Expose-Headers', 'x-auth'); 36 | next(); 37 | } 38 | } else { 39 | allowCrossDomain = function(req, res, next) { 40 | res.header('Access-Control-Allow-Origin', [ 41 | production, 42 | staging, 43 | staging2, 44 | local 45 | ]); 46 | res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE,PATCH'); 47 | res.header('Access-Control-Allow-Headers', 'Content-Type, x-auth'); 48 | res.header('Access-Control-Expose-Headers', 'x-auth'); 49 | next(); 50 | } 51 | } 52 | 53 | const port = process.env.PORT || 8080; 54 | 55 | //middleware 56 | //app.use(morgan('combined')); 57 | app.use(allowCrossDomain); 58 | app.use(bodyParser.json()); 59 | //serve up static public folder 60 | app.use(express.static(path.join(__dirname, '../public'), { 61 | maxAge: '1d' 62 | })); 63 | 64 | 65 | //listen to the required port 66 | http.listen(port, function() { 67 | console.log(`Express server listening on port ${port}`); 68 | }); 69 | 70 | 71 | process.on("uncaughtException", function(err) { 72 | console.log("uncaughtException:", err); 73 | }); 74 | 75 | require('./sockets/sockets').socketServer(io); 76 | router(app); 77 | -------------------------------------------------------------------------------- /public/style/chessboard-0.3.0.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * https://github.com/oakmac/chessboardjs/blob/master/LICENSE 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | /* clearfix */ 12 | .clearfix-7da63 { 13 | clear: both; 14 | } 15 | 16 | /* board */ 17 | .board-b72b1 { 18 | border: 2px solid #404040; 19 | -moz-box-sizing: content-box; 20 | box-sizing: content-box; 21 | } 22 | 23 | /* square */ 24 | .square-55d63 { 25 | float: left; 26 | position: relative; 27 | 28 | /* disable any native browser highlighting */ 29 | -webkit-touch-callout: none; 30 | -webkit-user-select: none; 31 | -khtml-user-select: none; 32 | -moz-user-select: none; 33 | -ms-user-select: none; 34 | user-select: none; 35 | } 36 | 37 | /* white square */ 38 | .white-1e1d7 { 39 | background-color: #f0d9b5; 40 | color: #b58863; 41 | } 42 | 43 | /* black square */ 44 | .black-3c85d { 45 | background-color: #b58863; 46 | color: #f0d9b5; 47 | } 48 | 49 | /* highlighted square */ 50 | .highlight1-32417, .highlight2-9c5d2 { 51 | -webkit-box-shadow: inset 0 0 3px 3px yellow; 52 | -moz-box-shadow: inset 0 0 3px 3px yellow; 53 | box-shadow: inset 0 0 3px 3px yellow; 54 | } 55 | 56 | /* disabled piece in crazyhouse hand */ 57 | .disabled-piece-b5259 { 58 | opacity: 0.15; 59 | } 60 | 61 | /* crazyhouse pieces */ 62 | .zh-pieces-5d8ff { 63 | background-color: rgba(160,160,160,0.6); 64 | border: black; 65 | border-width: 6px; 66 | border-style: double; 67 | } 68 | 69 | .zh-pieces-bottom-05b85 { 70 | margin-top: 3px; 71 | } 72 | 73 | .zh-pieces-top-de885 { 74 | margin-bottom: 3px; 75 | } 76 | 77 | .zh-pieces-5d8ff .piece-417db { 78 | display: inline-block; 79 | position: relative; 80 | } 81 | 82 | .zh-pieces-5d8ff .piece-417db::after { 83 | content: attr(data-number); 84 | position: absolute; 85 | bottom: 10%; 86 | right: 10%; 87 | font-weight: bold; 88 | font-size: 18px; 89 | padding: 3px; 90 | color: white; 91 | background-color: #999999; 92 | } 93 | 94 | /* notation */ 95 | .notation-322f9 { 96 | cursor: default; 97 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 98 | font-size: 14px; 99 | position: absolute; 100 | } 101 | .alpha-d2270 { 102 | bottom: 1px; 103 | right: 3px; 104 | } 105 | .numeric-fc462 { 106 | top: 2px; 107 | left: 2px; 108 | } 109 | 110 | .piece-417db { 111 | cursor: pointer; 112 | } -------------------------------------------------------------------------------- /src/reducers/index.js: -------------------------------------------------------------------------------- 1 | import {combineReducers} from 'redux'; 2 | import {reducer as notifications} from 'react-notification-system-redux'; 3 | import { reducer as formReducer } from 'redux-form'; 4 | 5 | import FourChess from '../../common/fourchess'; 6 | import rooms from './rooms_reducer'; 7 | import AuthReducer from './auth_reducer'; 8 | import newGameOptions from './new_game_reducer'; 9 | import currentProfile, {playerList, leaderboard, recentGames, matchmaking} from './profile_reducer'; 10 | import openThreads from './open_threads_reducer'; 11 | 12 | import {SELECTED_ROOM, LOGOUT_SUCCESS, CLOSE_ANALYSIS} from '../actions/types'; 13 | 14 | function activeThread (state = 200, action) { 15 | switch(action.type) { 16 | case SELECTED_ROOM: 17 | return action.payload; 18 | case LOGOUT_SUCCESS: 19 | return 'Games'; 20 | default: 21 | return state; 22 | } 23 | } 24 | 25 | 26 | function connection(state = {status: false, error: false}, action) { 27 | switch(action.type) { 28 | case 'duplicate-login': 29 | return {status: false, error: true}; 30 | case 'disconnect': 31 | return {status: false}; 32 | case 'connected-user': 33 | return {status: true}; 34 | default: 35 | return state; 36 | } 37 | } 38 | 39 | function userSearch(state = {}, action) { 40 | let newState = null; 41 | switch (action.type) { 42 | case 'user-search': 43 | newState = Object.assign({}, state); 44 | newState.userResults = action.payload; 45 | return newState; 46 | case 'challenged-player-id': 47 | newState = Object.assign({}, state); 48 | newState.selectedId = action.payload; 49 | return newState; 50 | default: 51 | return state; 52 | } 53 | } 54 | 55 | function settings(state = {}, action) { 56 | let newState = null; 57 | switch (action.type) { 58 | case 'change-zoom': 59 | newState = Object.assign({}, state); 60 | newState.zoomLevel = action.payload; 61 | return newState; 62 | default: 63 | return state; 64 | } 65 | } 66 | 67 | const rootReducer = combineReducers({ 68 | connection, 69 | notifications, //notification-center lib 70 | rooms, //A list of all available Chat Rooms 71 | activeThread, 72 | openThreads, 73 | currentProfile, 74 | newGameOptions, 75 | leaderboard, 76 | auth: AuthReducer, 77 | form: formReducer, 78 | recentGames, 79 | playerList, 80 | settings, 81 | matchmaking, 82 | userSearch 83 | }); 84 | 85 | export default rootReducer; 86 | -------------------------------------------------------------------------------- /server/game_models/players/Player.ts: -------------------------------------------------------------------------------- 1 | import Ratings from '../ratings/Ratings'; 2 | 3 | export default class Player { 4 | private _type: string = 'human'; //default player will be human 5 | private _alive: boolean = false; 6 | private _playerLevel: number; 7 | 8 | constructor( 9 | public _socket: any, 10 | private _playerId: string, 11 | private _username: string, 12 | private _picture: string, 13 | private _social: boolean, 14 | private _email: string, 15 | public standard_ratings: Ratings, 16 | public schess_ratings: Ratings, 17 | public fourplayer_ratings: Ratings, 18 | public crazyhouse_ratings: Ratings, 19 | public crazyhouse960_ratings: Ratings, 20 | public fullhouse_ratings: Ratings, 21 | private _IP: any, 22 | public anonymous: boolean) { 23 | } 24 | 25 | get alive(): boolean { return this._alive; } 26 | get playerLevel(): number { return this._playerLevel; } 27 | get ipaddress(): any { return this._IP; } 28 | 29 | set alive(alive: boolean) { this._alive = alive; } 30 | set playerLevel(num: number) { this._playerLevel = num; } 31 | 32 | getPlayer(): any { 33 | return { 34 | username: this._username, 35 | playerId: this._playerId, 36 | picture: this._picture, 37 | standard_ratings: this.standard_ratings, 38 | schess_ratings: this.schess_ratings, 39 | fourplayer_ratings: this.fourplayer_ratings, 40 | crazyhouse_ratings: this.crazyhouse_ratings, 41 | crazyhouse960_ratings: this.crazyhouse960_ratings, 42 | fullhouse_ratings: this.fullhouse_ratings, 43 | alive: this._alive, 44 | level: this._playerLevel, 45 | type: this._type, 46 | anonymous: this.anonymous 47 | }; 48 | } 49 | 50 | emitMessage() { 51 | 52 | } 53 | 54 | get picture(): string { 55 | return this._picture; 56 | } 57 | 58 | get type(): string { 59 | return this._type; 60 | } 61 | 62 | set type(type: string) { 63 | this._type = type; 64 | } 65 | 66 | get socket() { 67 | return this._socket; 68 | } 69 | 70 | get username(): string { 71 | return this._username; 72 | } 73 | 74 | set username(username: string) { 75 | if(username.length > 3) { 76 | this._username = username; 77 | } 78 | } 79 | 80 | get playerId(): string { 81 | return this._playerId; 82 | } 83 | 84 | set playerId(id: string) { 85 | this._playerId = id; 86 | } 87 | } -------------------------------------------------------------------------------- /src/containers/pgn/pgn_prev_button.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | import {Button} from 'react-bootstrap'; 5 | 6 | class PGNPrevButton extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | componentDidMount() { 11 | this.bindArrowKey(); 12 | } 13 | componentDidUpdate() { 14 | this.bindArrowKey(); 15 | } 16 | componentWillUnmount() { 17 | $(document).off('keydown'); 18 | } 19 | bindArrowKey() { 20 | $(document).on('keydown', this.onArrowKey.bind(this)); 21 | } 22 | onArrowKey(event) { 23 | let keyCode = '37'; // left arrow 24 | let tag = event.target.tagName.toLowerCase(); 25 | if (event.which == keyCode && tag !== 'input') { 26 | this.onClick(); 27 | event.preventDefault(); 28 | event.stopImmediatePropagation(); 29 | return false; 30 | } 31 | } 32 | onClick() { 33 | let roomName = this.props.room.room.name; 34 | let lastPly = this.props.pgn.length; 35 | let activePly; 36 | if (typeof this.props.activePly === "undefined") { 37 | activePly = lastPly; 38 | } else { 39 | activePly = this.props.activePly; 40 | } 41 | let prevPly = Math.max(0, activePly - 1); 42 | this.props.changeActivePly(roomName, prevPly); 43 | } 44 | disabled() { 45 | return ( 46 | typeof this.props.activePly === "undefined" || 47 | this.props.activePly === 0 48 | ); 49 | } 50 | render() { 51 | return ( 52 | 59 | ); 60 | } 61 | 62 | } 63 | 64 | function mapStateToProps(state, props) { 65 | let activeThread = state.activeThread; 66 | let room = state.openThreads[activeThread]; 67 | let game = room.game; 68 | let pgn = game.pgn; 69 | let activePly = room.activePly; 70 | return { 71 | activeThread: activeThread, 72 | room: room, 73 | game: game, 74 | pgn: pgn, 75 | activePly: activePly 76 | } 77 | } 78 | 79 | function mapDispatchToProps(dispatch) { 80 | return { 81 | changeActivePly: (roomName, ply) => { 82 | dispatch(changeActivePly(roomName, ply)) 83 | } 84 | } 85 | } 86 | 87 | export default connect(mapStateToProps, mapDispatchToProps)(PGNPrevButton); -------------------------------------------------------------------------------- /src/containers/pgn/pgn_next_button.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import {changeActivePly} from '../../actions/room'; 4 | import {Button} from 'react-bootstrap'; 5 | 6 | class PGNNextButton extends Component { 7 | constructor(props) { 8 | super(props); 9 | } 10 | shouldComponentUpdate(nextProps) { 11 | return true; 12 | } 13 | componentDidMount() { 14 | this.bindArrowKey(); 15 | } 16 | componentWillUnmount() { 17 | $(document).off('keydown'); 18 | } 19 | bindArrowKey() { 20 | $(document).on('keydown', this.onArrowKey.bind(this)); 21 | } 22 | onArrowKey(event) { 23 | let keyCode = '39'; // right arrow 24 | let tag = event.target.tagName.toLowerCase(); 25 | if (event.which == keyCode && tag !== 'input') { 26 | this.onClick(); 27 | event.preventDefault(); 28 | event.stopImmediatePropagation(); 29 | return false; 30 | } 31 | } 32 | onClick() { 33 | let roomName = this.props.room.room.name; 34 | let lastPly = this.props.pgn.length; 35 | let activePly; 36 | if (typeof this.props.activePly === "undefined") { 37 | activePly = lastPly; 38 | } else { 39 | activePly = this.props.activePly; 40 | } 41 | let nextPly = Math.min(lastPly, activePly + 1); 42 | this.props.changeActivePly(roomName, nextPly); 43 | } 44 | disabled() { 45 | return ( 46 | typeof this.props.activePly === "undefined" || 47 | this.props.activePly === this.props.pgn.length 48 | ); 49 | } 50 | render() { 51 | return ( 52 | 59 | ); 60 | } 61 | 62 | } 63 | 64 | function mapStateToProps(state, props) { 65 | let activeThread = state.activeThread; 66 | let room = state.openThreads[activeThread]; 67 | let game = room.game; 68 | let pgn = game.pgn; 69 | let activePly = room.activePly; 70 | return { 71 | activeThread: activeThread, 72 | room: room, 73 | game: game, 74 | pgn: pgn, 75 | activePly: activePly 76 | } 77 | } 78 | 79 | function mapDispatchToProps(dispatch) { 80 | return { 81 | changeActivePly: (roomName, ply) => { 82 | dispatch(changeActivePly(roomName, ply)) 83 | } 84 | } 85 | } 86 | 87 | export default connect(mapStateToProps, mapDispatchToProps)(PGNNextButton); -------------------------------------------------------------------------------- /src/containers/notification_handler.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import {connect} from 'react-redux'; 3 | import BrowserNotification from '../components/room/browser_notification'; 4 | 5 | class NotificationHandler extends Component { 6 | constructor(props) { 7 | super(props); 8 | } 9 | 10 | componentWillReceiveProps(nextProps) { 11 | let gameStarting = this.props.gameStarted === false && nextProps.gameStarted === true; 12 | let userIsPlaying = this.userIsPlaying(nextProps); 13 | let tabHasFocus = this.tabHasFocus(); 14 | if (gameStarting && userIsPlaying && !tabHasFocus) { 15 | this.ignore = false; 16 | this.title = "Game started"; 17 | this.body = `Your game in room ${this.props.room.room.name} has started!`; 18 | this.options = { 19 | body: this.body 20 | }; 21 | } 22 | } 23 | 24 | userIsPlaying(props) { 25 | let colors = ['white', 'black', 'gold', 'red']; 26 | if (typeof props.game === "undefined") { 27 | return false; 28 | } 29 | return colors.some((color) => { 30 | if (typeof props.game[color] !== "undefined") { 31 | if (props.game[color].playerId === props.profile._id) { 32 | return true; 33 | } 34 | } 35 | }); 36 | } 37 | 38 | tabHasFocus() { 39 | let hidden = hidden; 40 | if (typeof document.hidden !== "undefined") { 41 | hidden = "hidden"; 42 | } else if (typeof document.msHidden !== "undefined") { 43 | hidden = "msHidden"; 44 | } 45 | return !(document[hidden]); 46 | } 47 | 48 | render() { 49 | return ( 50 | 55 | ); 56 | } 57 | } 58 | 59 | function mapStateToProps(state) { 60 | let profile = state.auth.profile; 61 | let name = state.activeThread; 62 | let room = state.openThreads[name]; 63 | let game, move, fen, pgn, gameStarted, activePly; 64 | if (typeof room !== "undefined") { 65 | game = room.game; 66 | move = game.move; 67 | fen = game.fen; 68 | pgn = game.pgn; 69 | gameStarted = game.gameStarted; 70 | activePly = room.activePly; 71 | } 72 | return { 73 | profile: profile, 74 | move: move, 75 | room: room, 76 | game: game, 77 | name: name, 78 | fen: fen, 79 | pgn: pgn, 80 | gameStarted: gameStarted, 81 | activePly: activePly 82 | } 83 | } 84 | 85 | 86 | export default connect(mapStateToProps) (NotificationHandler) -------------------------------------------------------------------------------- /src/containers/room_info.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | class RoomInfo extends Component { 5 | constructor() { 6 | super(); 7 | } 8 | 9 | gameTypeString() { 10 | switch (this.props.gameType) { 11 | case 'four-player': 12 | return 'Four-Player'; 13 | case 'standard': 14 | return 'Standard'; 15 | case 'crazyhouse': 16 | return 'Crazyhouse'; 17 | case 'crazyhouse960': 18 | return 'Crazyhouse 960'; 19 | case 'schess': 20 | return 'S-Chess'; 21 | case 'fullhouse-chess': 22 | return 'Fullhouse Chess'; 23 | } 24 | } 25 | 26 | roomModeString() { 27 | switch (this.props.roomMode) { 28 | case 'open-table': 29 | return 'Open Table'; 30 | case 'match': 31 | return 'Match'; 32 | } 33 | } 34 | 35 | render() { 36 | let time_increment = this.props.time_increment ? this.props.time_increment : 0; 37 | return ( 38 |
    39 |
    40 |
    41 | 42 | {this.props.roomName} 43 | 44 |
    45 |
    46 | 47 | {this.props.time_base}+{time_increment} 48 | 49 | 50 | 51 | {this.gameTypeString()} 52 | 53 |
    54 |
    55 | 56 | {this.roomModeString()} 57 | 58 |
    59 |
    60 |
    61 | ); 62 | } 63 | } 64 | 65 | function mapStateToProps(state) { 66 | let room = state.openThreads[state.activeThread]; 67 | if (room) { 68 | let time_base = room.time.value; 69 | let time_increment = room.time.increment; 70 | let roomMode = room.room.roomMode; 71 | let gameType = room.gameType; 72 | let roomName = room.room.name; 73 | return { 74 | profile: state.auth.profile, 75 | room, 76 | time_base, 77 | time_increment, 78 | roomMode, 79 | gameType, 80 | roomName 81 | }; 82 | } 83 | return {}; 84 | } 85 | 86 | export default connect(mapStateToProps)(RoomInfo) -------------------------------------------------------------------------------- /common/Clock.js: -------------------------------------------------------------------------------- 1 | 2 | export default class Clock { 3 | constructor(baseTime, increment) { 4 | if (typeof baseTime !== "undefined") 5 | this.baseTime = baseTime; 6 | if (typeof increment !== "undefined") 7 | this.increment = increment; 8 | this.duration = baseTime; 9 | this.granularity = 1000; 10 | this.running = false; 11 | this.timer = null; 12 | this.startTime = null; 13 | this.tickCallbacks = []; 14 | this.timeUpCallbacks = []; 15 | } 16 | 17 | start(duration) { 18 | if (this.running) { 19 | return; 20 | } 21 | if (typeof duration !== "undefined") { 22 | this.duration = duration; 23 | } 24 | this.running = true; 25 | this.startTime = Date.now(); 26 | let diff, timeString; 27 | let that = this; 28 | (function timer() { 29 | diff = that.duration - (Date.now() - that.startTime); 30 | if (diff < 10) { 31 | that.timeUpCallbacks.forEach(function(callback) { 32 | callback.call(this, diff); 33 | }, that); 34 | that.pause(); 35 | return; 36 | } 37 | if (diff <= 2000) { 38 | that.granularity = 10; 39 | } else { 40 | that.granularity = 100; 41 | } 42 | that.timer = setTimeout(timer, that.granularity); 43 | that.tickCallbacks.forEach(function(callback) { 44 | callback.call(this, diff); 45 | }, that); 46 | }()); 47 | } 48 | 49 | onTick(callback) { 50 | if (typeof callback === 'function') { 51 | this.tickCallbacks.push(callback); 52 | } 53 | return this; 54 | } 55 | 56 | onTimeUp(callback) { 57 | if (typeof callback === "function") { 58 | this.timeUpCallbacks.push(callback); 59 | } 60 | return this; 61 | } 62 | 63 | pause() { 64 | if (!this.running) { 65 | return; 66 | } 67 | this.running = false; 68 | if (this.timer) { 69 | clearTimeout(this.timer); 70 | } 71 | this.timer = null; 72 | this.duration -= Date.now() - this.startTime; 73 | } 74 | 75 | static parse(millis) { 76 | let minutes = Math.floor(millis / 60000); 77 | let seconds = ((millis % 60000) / 1000); 78 | if(Math.floor(seconds) == 60) { 79 | minutes++; 80 | seconds = 0; 81 | } 82 | minutes = Math.max(0, minutes); 83 | seconds = Math.max(0, seconds); 84 | if (millis < 10000) { 85 | seconds = seconds.toFixed(1); 86 | } else { 87 | seconds = Math.floor(seconds); 88 | } 89 | let timeString = minutes + ":" + (seconds < 10 ? '0' : '') + seconds; 90 | return timeString; 91 | } 92 | } -------------------------------------------------------------------------------- /src/components/auth/signup_form.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { Field, reduxForm } from 'redux-form'; 3 | import {FormGroup, FormControl, ControlLabel, Button} from 'react-bootstrap'; 4 | 5 | const email = value => 6 | value && !/^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i.test(value) ? 7 | 'Invalid email address' : undefined 8 | 9 | class SignUpForm extends Component { 10 | 11 | 12 | renderField ({ input, label, placeholder, type, meta: { touched, error, warning } }) { 13 | 14 | return ( 15 |
    16 | 17 | 18 | {touched && (error &&
    {error}
    )} 19 | 20 |
    21 | ) 22 | } 23 | 24 | render() { 25 | const { handleSubmit } = this.props; 26 | return ( 27 |
    29 | 30 | 36 | 37 | 43 | 44 | 50 | 51 | 52 | 57 |
    58 | ); 59 | } 60 | } 61 | 62 | //Validate the form 63 | const validate = (values) => { 64 | const errors = {}; 65 | 66 | if(values.signUpPassword !== values.vSignUpPassword) { 67 | errors.signUpPassword = 'Passwords do not match!' 68 | } 69 | 70 | if(!values.signUpPassword) { 71 | errors.signUpPassword = "Please enter a password"; 72 | } 73 | 74 | if(!values.signUpEmail) { 75 | errors.signUpEmail = " Please enter an email"; 76 | } 77 | 78 | if(!values.vSignUpPassword) { 79 | errors.vSignUpPassword = "Please enter a password confirmation"; 80 | } 81 | 82 | return errors; 83 | } 84 | 85 | SignUpForm = reduxForm({ 86 | form: 'signUpForm', 87 | validate 88 | })(SignUpForm) 89 | 90 | export default SignUpForm; 91 | -------------------------------------------------------------------------------- /src/containers/board/promotion_selector.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | class PromotionSelector extends Component { 5 | constructor() { 6 | super(); 7 | } 8 | 9 | pieceTypes() { 10 | let pieceTypes; 11 | console.log("promotion. game type:", this.props.gameType); 12 | switch(this.props.gameType) { 13 | case 'schess': 14 | pieceTypes = ['e', 'h', 'q', 'r', 'n', 'b']; 15 | break; 16 | case 'fullhouse-chess': 17 | pieceTypes = ['q', 'r', 'n', 'b', 'v']; 18 | break; 19 | default: 20 | pieceTypes = ['q', 'r', 'n', 'b']; 21 | break; 22 | } 23 | console.log("piece types:", JSON.stringify(pieceTypes)); 24 | return pieceTypes; 25 | } 26 | 27 | renderPieceButton(color, piece) { 28 | let pieceCode = color + piece.toUpperCase(); 29 | let imgName = pieceCode + ".png"; 30 | return ( 31 | 38 | ); 39 | } 40 | 41 | onPieceButtonClick() { 42 | let piece = arguments[0]; 43 | this.props.callback(piece); 44 | } 45 | 46 | onPromotionClose() { 47 | this.props.onPromotionClose(); 48 | } 49 | 50 | render() { 51 | let pieceTypes = this.pieceTypes(); 52 | let color = this.props.color; 53 | let className = this.props.visible ? "visible" : "hidden"; 54 | return ( 55 |
    59 |
    60 | {pieceTypes.map(p => this.renderPieceButton(color, p))} 61 |
    62 |
    63 | ); 64 | } 65 | } 66 | 67 | function mapStateToProps(state) { 68 | let profile = state.auth.profile; 69 | let name = state.activeThread; 70 | let room = state.openThreads[name]; 71 | let game = room.game; 72 | let color = 'w'; 73 | if (game.white && game.white.playerId === profile._id) { 74 | color = 'w'; 75 | } else if (game.black && game.black.playerId === profile._id) { 76 | color = 'b'; 77 | } else if (game.red && game.red.playerId === profile._id) { 78 | color = 'r'; 79 | } else if (game.gold && game.gold.playerId === profile._id) { 80 | color = 'g'; 81 | } 82 | let visible = room.promotionVisible; 83 | let callback = room.promotionCallback; 84 | return { 85 | color, 86 | visible, 87 | callback, 88 | gameType: game.gameType, 89 | }; 90 | } 91 | 92 | export default connect(mapStateToProps) (PromotionSelector) -------------------------------------------------------------------------------- /src/containers/existing_room_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import Select from 'react-select'; 3 | import { connect } from 'react-redux'; 4 | import { mapObject } from '../utils/' 5 | import { joinRoom, selectedRoom } from '../actions/'; 6 | 7 | import {Row, Col, Button, Table} from 'react-bootstrap'; 8 | 9 | const gameTypeOptions = [ 10 | { value: 'all', label: 'All Games'}, 11 | { value: 'two-player', label: 'Two Player'}, 12 | { value: 'four-player', label: 'Four Player'}, 13 | { value: 'four-player-team', label: 'Four Player Teams'} 14 | ] 15 | 16 | 17 | class ExistingRoomList extends Component { 18 | constructor (props) { 19 | super(props) 20 | 21 | this.renderRoomItems = this.renderRoomItems.bind(this); 22 | } 23 | 24 | onClickRoom(room, event) { 25 | this.props.joinRoom(room.room.name, room); 26 | this.props.selectedRoom(room.room.name); 27 | } 28 | 29 | renderRoomItems(room) { 30 | return ( 31 | 32 | {room.room.name} 33 | {room.gameType} 34 | {`${room.time.value}+${room.time.increment}` } 35 | {room.numPlayers} 36 | 37 | ); 38 | } 39 | 40 | render() { 41 | const { rooms } = this.props; 42 | return ( 43 |
    44 | 45 | 46 | 47 | 48 |
    49 |
    50 | 51 |
    52 |
    53 | 54 | 55 | {rooms.length} Game Rooms 56 | 57 | 58 |
    59 |
    60 |
    61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | {rooms.map(this.renderRoomItems)} 73 | 74 |
    TitleGame TypeTime Control# Users
    75 | 76 |
    77 | ); 78 | } 79 | } 80 | 81 | function mapStateToProps(state) { 82 | // console.log(state); 83 | return { 84 | rooms: state.rooms 85 | } 86 | } 87 | 88 | export default connect(mapStateToProps, {joinRoom, selectedRoom}) (ExistingRoomList) 89 | -------------------------------------------------------------------------------- /server/engine/TwoEngine.ts: -------------------------------------------------------------------------------- 1 | import Engine from './Engine'; 2 | const {ab2str} = require('../utils/utils'); 3 | 4 | import Room from '../game_models/rooms/Room'; 5 | import Connection from '../sockets/Connection'; 6 | import Game from '../game_models/games/Game'; 7 | 8 | export default class TwoEngine extends Engine { 9 | constructor(roomName, increment, connection) { 10 | super('stockfish_8_x64', roomName, connection, false); 11 | this.setDepth(15); 12 | this.increment = increment * 1000; // s -> ms 13 | } 14 | onBestMove(data) { 15 | var str = ab2str(data); 16 | if (str.indexOf("uciok") !== -1) { 17 | this.setupOptions(); 18 | } 19 | if(str.indexOf("bestmove") !== -1) { 20 | // move format: e2e3 21 | let startIndex = str.indexOf("bestmove") + 9; 22 | let from = str.substring(startIndex, startIndex + 2); 23 | let to = str.substring(startIndex + 2, startIndex + 4); 24 | let compMove = { 25 | to: to, 26 | from: from, 27 | promotion: 'q' 28 | }; 29 | 30 | let roomName = this.roomName; 31 | let room: Room = this.connection.getRoomByName(roomName); 32 | if(!room) { 33 | return; 34 | } 35 | let game: Game = room.game; 36 | let move = data.move; 37 | room.makeMove(compMove, Date.now()); 38 | } 39 | } 40 | 41 | setupOptions() { 42 | this.setOption("Skill Level", "1"); 43 | this.setOption("Contempt", "100"); 44 | this.setOption("Move Overhead", "300"); 45 | } 46 | 47 | setPosition(fen) { 48 | if(this.engine) { 49 | this.engine.stdin.write( 50 | "position fen " + fen + "\n" 51 | ); 52 | } 53 | } 54 | 55 | setTurn(turnColor) { 56 | // console.log("set turn on TwoEngine"); 57 | } 58 | 59 | setOut(colorOut) { 60 | if(this.engine) { 61 | this.engine.stdin.write("stop"); 62 | } 63 | } 64 | 65 | adjustDepth(timeLeft) { 66 | let depth = this.depth; 67 | return depth; 68 | } 69 | 70 | go(timeLeft, level) { 71 | this.timeLeft = timeLeft; 72 | if (level) { 73 | // console.log("setting comp skill level to", level); 74 | this.setOption("Skill Level", "" + level); 75 | } 76 | let timeString; 77 | if (timeLeft) { 78 | timeString = " wtime "+timeLeft+" btime "+timeLeft+" "; 79 | timeString += "winc "+this.increment+" binc "+this.increment+" "; 80 | } else { 81 | timeString = ""; 82 | } 83 | let goString; 84 | if(this.mode == 0) { 85 | goString = "go" + timeString + "\n"; 86 | // console.log("[engine: "+this.roomName+"]", goString); 87 | if(this.engine) { 88 | this.engine.stdin.write(goString); 89 | } 90 | } else { 91 | goString = "go " + "depth 4" + "\n"; 92 | this.engine.stdin.write(goString); 93 | } 94 | 95 | } 96 | } -------------------------------------------------------------------------------- /src/components/room/room.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import RoomUserList from './room_user_list'; 3 | import MessageList from '../chat_message/message_list'; 4 | import RoomSettings from './room_settings'; 5 | import MessageSend from '../../containers/message_send'; 6 | 7 | import {Row, Col, Modal, Button} from 'react-bootstrap'; 8 | 9 | 10 | export default class Room extends Component { 11 | constructor(props) { 12 | super(props); 13 | this.state = { 14 | showModal: false 15 | } 16 | } 17 | 18 | shouldComponentUpdate(nextProps) { 19 | if(!this.props.chat.room.name != nextProps.chat.room.name) { 20 | return true; 21 | } 22 | if(nextProps.chat.numMessages != this.props.chat.numMessages) { 23 | return true; 24 | } 25 | return false; 26 | } 27 | 28 | openUserModal() { 29 | this.setState({ 30 | showModal: true 31 | }); 32 | } 33 | 34 | closeUserModal() { 35 | this.setState({ 36 | showModal: false 37 | }); 38 | } 39 | 40 | render() { 41 | const {index, chat, active} = this.props; 42 | if(chat.mode === 'analysis') { 43 | return
    In Analysis Mode!
    44 | } else { 45 | return ( 46 |
    50 | 51 | 52 | 53 | 55 | {chat.users.length} users 56 | 57 | 58 | 59 | Room Members 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 |
    77 | ); 78 | } 79 | 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/utils/index.js: -------------------------------------------------------------------------------- 1 | export function mapObject(object, callback) { 2 | return Object.keys(object).map(function (key) { 3 | return callback(key, object[key]); 4 | }); 5 | } 6 | 7 | export function generateTokenHeader() { 8 | return { 9 | headers: { 10 | "x-auth": localStorage.getItem('token') 11 | } 12 | }; 13 | } 14 | 15 | export function formatTurn(turn) { 16 | switch (turn) { 17 | case 'w': 18 | return 'white'; 19 | case 'b': 20 | return 'black'; 21 | case 'g': 22 | return 'gold'; 23 | case 'r': 24 | return 'red'; 25 | } 26 | } 27 | 28 | export function showElo(game, time, player) { 29 | if(!game || !time || !player || !time.value || !time.value) { 30 | return ""; 31 | } 32 | 33 | let ratings, tcIndex; 34 | //this time estimate is based on an estimated game length of 35 moves 35 | let totalTimeMs = (time.value * 60 * 1000) + (35 * time.increment * 1000); 36 | 37 | //Two player cutoff times 38 | let twoMins = 120000; //two minutes in ms 39 | let eightMins = 480000; 40 | let fifteenMins = 900000; 41 | 42 | //four player cutoff times 43 | let fourMins = 240000; 44 | let twelveMins = 720000; 45 | let twentyMins = 12000000; 46 | 47 | switch(game.gameType) { 48 | case 'standard': 49 | ratings = "standard_ratings"; 50 | break; 51 | case 'schess': 52 | ratings = "schess_ratings"; 53 | break; 54 | case 'crazyhouse': 55 | ratings = "crazyhouse_ratings"; 56 | break; 57 | case 'crazyhouse960': 58 | ratings = "crazyhouse960_ratings"; 59 | break; 60 | case 'four-player': 61 | ratings = 'fourplayer_ratings'; 62 | break; 63 | case 'fullhouse-chess': 64 | ratings = 'fullhouse_ratings'; 65 | break; 66 | } 67 | switch(game.gameType) { 68 | case 'standard': 69 | case 'schess': 70 | case 'crazyhouse': 71 | case 'crazyhouse960': 72 | case 'fullhouse-chess': 73 | if( totalTimeMs <= twoMins) { 74 | //bullet 75 | tcIndex = 'bullet'; 76 | } else if(totalTimeMs <= eightMins) { 77 | //blitz 78 | tcIndex = 'blitz'; 79 | } else if(totalTimeMs <= fifteenMins) { 80 | //rapid 81 | tcIndex = 'rapid'; 82 | } else { 83 | //classical 84 | tcIndex = 'classic'; 85 | } 86 | 87 | if(!player[ratings] || !player[ratings][tcIndex]) { 88 | return ""; 89 | } 90 | 91 | return player[ratings][tcIndex]; 92 | case 'four-player': 93 | if( totalTimeMs <= fourMins) { 94 | //bullet 95 | tcIndex = 'bullet'; 96 | } else if(totalTimeMs <= twelveMins) { 97 | //blitz 98 | tcIndex = 'blitz'; 99 | } else if(totalTimeMs <= twentyMins) { 100 | //rapid 101 | tcIndex = 'rapid'; 102 | } else { 103 | //classical 104 | tcIndex = 'classic'; 105 | } 106 | if(!player[ratings] || !player[ratings][tcIndex]) { 107 | return ""; 108 | } 109 | 110 | return player[ratings][tcIndex]; 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/containers/new_game.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import Select from 'react-select'; 4 | 5 | import NewGameModalGameOptions from '../components/create_game/new_game_modal_game_options'; 6 | import NewGameModalRoomOptions from '../components/create_game/new_game_modal_room_options'; 7 | 8 | import {Button, Modal} from 'react-bootstrap'; 9 | 10 | import {createGameRoom, resetNewGameModal, finalizeGameRoom} from '../actions/create_game'; 11 | 12 | 13 | class NewGame extends Component { 14 | 15 | constructor(props) { 16 | super(props); 17 | 18 | this.renderModal = this.renderModal.bind(this); 19 | this.renderGameOptions = this.renderGameOptions.bind(this); 20 | this.state = { showModal: false }; 21 | } 22 | 23 | close() { 24 | this.setState({ showModal: false }); 25 | this.props.resetNewGameModal(); 26 | } 27 | 28 | open() { 29 | this.setState({ showModal: true }); 30 | } 31 | 32 | onCreateGame() { 33 | this.props.createGameRoom(); 34 | } 35 | 36 | renderRoomOptions() { 37 | return ( 38 | this.challengedPlayerInput = input} /> 40 | ); 41 | } 42 | 43 | renderGameOptions() { 44 | return ( 45 | this.roomNameInput = input} /> 47 | ); 48 | } 49 | 50 | submitRoom() { 51 | this.props.resetNewGameModal(); 52 | this.props.room.name = this.roomNameInput.value; 53 | this.props.room.challengedPlayerUsername = this.challengedPlayerInput.value; 54 | this.props.finalizeGameRoom(this.props.game, this.props.profile); 55 | this.close(); 56 | } 57 | 58 | renderModalFooter() { 59 | return ( 60 |
    61 | 64 | 67 |
    68 | ); 69 | } 70 | 71 | renderModal() { 72 | 73 | return ( 74 | 75 | 76 | New Game Room 77 | 78 | 79 | {this.renderRoomOptions()} 80 | 81 | 82 | {this.renderGameOptions()} 83 | 84 | 85 | {this.renderModalFooter()} 86 | 87 | 88 | ); 89 | } 90 | 91 | render() { 92 | return ( 93 |
    94 | 99 | 100 | {this.renderModal()} 101 |
    102 | ); 103 | } 104 | 105 | } 106 | 107 | function mapStateToProps(state) { 108 | return { 109 | makingGameRoom: state.newGameOptions.isMakingGameRoom, 110 | room: state.newGameOptions.room, 111 | game: state.newGameOptions, 112 | profile: state.auth.profile 113 | } 114 | } 115 | 116 | export default connect(mapStateToProps, {createGameRoom, resetNewGameModal, finalizeGameRoom}) (NewGame); 117 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Hello_Chess", 3 | "version": "2.0.0", 4 | "description": "Live Chess Server", 5 | "main": "public/index.jsx", 6 | "repository": "", 7 | "scripts": { 8 | "start": "node ./node_modules/webpack-dev-server/bin/webpack-dev-server.js", 9 | "server": "nodemon -e js,ts --exec ./node_modules/.bin/ts-node -- ./server/server.ts", 10 | "postinstall": "webpack -p", 11 | "test": "mocha --compilers js:babel-core/register --require ./test/test_helper.js --recursive ./test", 12 | "test:watch": "npm run test -- --watch" 13 | }, 14 | "author": "John V. Flickinger, Joe Ksiazek", 15 | "license": "MIT", 16 | "devDependencies": { 17 | "@types/node": "^7.0.22", 18 | "babel-core": "^6.2.1", 19 | "babel-loader": "^6.2.0", 20 | "babel-polyfill": "^6.23.0", 21 | "babel-preset-es2015": "^6.1.18", 22 | "babel-preset-react": "^6.1.18", 23 | "chai": "^3.5.0", 24 | "chai-jquery": "^2.0.0", 25 | "css-loader": "^0.27.3", 26 | "jquery": "^2.2.1", 27 | "jsdom": "^8.1.0", 28 | "mocha": "^2.4.5", 29 | "node-sass": "^4.5.3", 30 | "react-addons-test-utils": "^0.14.7", 31 | "reapop": "^1.1.1", 32 | "style-loader": "^0.14.1", 33 | "ts-node": "^3.0.4", 34 | "typescript": "^2.3.3", 35 | "webpack": "^3.0.0", 36 | "webpack-dev-server": "^1.16.5" 37 | }, 38 | "dependencies": { 39 | "async": "^2.4.1", 40 | "axios": "^0.15.3", 41 | "babel-polyfill": "^6.23.0", 42 | "babel-preset-stage-1": "^6.1.18", 43 | "bcryptjs": "^2.4.0", 44 | "body-parser": "^1.17.2", 45 | "chess.js": "^0.10.2", 46 | "chessboardjs": "0.0.1", 47 | "cors": "^2.8.3", 48 | "crazyhouse.js": "0.0.8", 49 | "crypto-js": "^3.1.9-1", 50 | "dotenv": "^4.0.0", 51 | "elo-js": "^1.0.1", 52 | "events": "^1.1.1", 53 | "express": "^4.15.3", 54 | "font-awesome": "^4.7.0", 55 | "howler": "^2.0.3", 56 | "html-webpack-plugin": "^2.28.0", 57 | "http": "0.0.0", 58 | "jquery-typeahead": "^2.8.0", 59 | "jsonwebtoken": "^7.4.1", 60 | "jwt-decode": "^2.1.0", 61 | "lodash": "^3.10.1", 62 | "mongodb": "^2.2.27", 63 | "mongoose": "^4.10.3", 64 | "mongoose-paginate": "^5.0.3", 65 | "morgan": "^1.8.2", 66 | "normalizr": "^3.2.3", 67 | "passport": "^0.3.2", 68 | "passport-facebook-token": "^3.3.0", 69 | "passport-google-plus-token": "^2.1.0", 70 | "passport-jwt": "^2.2.1", 71 | "passport-local": "^1.0.0", 72 | "path": "^0.12.7", 73 | "rc-slider": "^6.3.1", 74 | "react": "^15.4.2", 75 | "react-bootstrap": "^0.30.10", 76 | "react-bootstrap-slider": "^1.1.3", 77 | "react-bootstrap-sweetalert": "^3.0.0", 78 | "react-bootstrap-tabs": "0.0.1", 79 | "react-dom": "^15.4.2", 80 | "react-facebook-login": "^3.6.1", 81 | "react-google-login": "^2.8.9", 82 | "react-infinite-scroller": "^1.0.12", 83 | "react-loading": "0.0.9", 84 | "react-loading-animation": "^1.2.1", 85 | "react-modal-dialog": "^4.0.3", 86 | "react-notification-system": "^0.2.14", 87 | "react-notification-system-redux": "^1.1.2", 88 | "react-popout": "^0.6.0", 89 | "react-redux": "^4.0.0", 90 | "react-resizable": "^1.7.1", 91 | "react-router": "^2.8.1", 92 | "react-router-bootstrap": "^0.23.3", 93 | "react-select": "^1.0.0-rc.5", 94 | "react-timeout": "^1.0.1", 95 | "react-toggle-button": "^2.1.0", 96 | "react-web-notification": "^0.2.4", 97 | "react-youtube": "^7.4.0", 98 | "redux": "^3.0.4", 99 | "redux-form": "^6.7.0", 100 | "redux-promise": "^0.5.3", 101 | "redux-socket.io": "^1.4.0", 102 | "redux-thunk": "^2.2.0", 103 | "schess.js": "0.0.8", 104 | "socket.io": "^1.7.4", 105 | "socket.io-client": "^1.7.4", 106 | "validator": "^6.2.0" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/containers/user/player_list.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import { browserHistory } from 'react-router'; 4 | import { mapObject } from '../../utils'; 5 | 6 | import { getPlayerList } from '../../actions/user'; 7 | import {Panel, Button, Thumbnail, Col, Row, Grid} from 'react-bootstrap'; 8 | import InfiniteScroll from 'react-infinite-scroller'; 9 | 10 | 11 | class PlayerList extends Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | componentWillMount() { 18 | this.props.getPlayerList(0); 19 | } 20 | 21 | loadMore(i) { 22 | this.props.getPlayerList(i); 23 | } 24 | 25 | back(e) { 26 | e.preventDefault(); 27 | browserHistory.push('/live'); 28 | } 29 | 30 | renderPlayer(player) { 31 | return ( 32 | 33 |
    34 | ... 35 |
    36 |

    {player.username ? player.username : "unknown"}

    37 |

    ...

    38 |

    39 | 44 |

    45 |
    46 |
    47 | 48 | ); 49 | } 50 | 51 | render() { 52 | let { playerList, hasMore } = this.props; 53 | if(!playerList) { 54 | return ( 55 | 56 | 59 | 60 | 61 | 62 | ); 63 | } else { 64 | return ( 65 |
    66 | 69 | 70 | 71 | 72 | 73 |
    74 |

    75 | 76 | PLAYERS 77 | 78 |

    79 |
    80 | Loading ...
    } 85 | > 86 | { this.props.playerList.map(this.renderPlayer.bind(this)) } 87 | 88 | 89 | 90 | 91 | ); 92 | } 93 | 94 | } 95 | } 96 | function mapStateToProps(state) { 97 | return { 98 | playerList: state.playerList.players, 99 | hasMore: state.playerList.hasMore, 100 | } 101 | } 102 | 103 | export default connect (mapStateToProps, {getPlayerList}) (PlayerList); 104 | -------------------------------------------------------------------------------- /src/components/chat_message/message_list_item.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { browserHistory } from 'react-router'; 3 | import {ListGroupItem, Row, Col} from 'react-bootstrap' 4 | 5 | function formatTime(startTime) { 6 | startTime = new Date(startTime); 7 | 8 | // later record end time 9 | var endTime = new Date(); 10 | 11 | // time difference in ms 12 | var timeDiff = endTime - startTime; 13 | 14 | // strip the ms 15 | timeDiff /= 1000; 16 | 17 | // get seconds (Original had 'round' which incorrectly counts 0:28, 0:29, 1:30 ... 1:59, 1:0) 18 | var seconds = Math.round(timeDiff % 60); 19 | 20 | // remove seconds from the date 21 | timeDiff = Math.floor(timeDiff / 60); 22 | 23 | // get minutes 24 | var minutes = Math.round(timeDiff % 60); 25 | 26 | // remove minutes from the date 27 | timeDiff = Math.floor(timeDiff / 60); 28 | 29 | // get hours 30 | var hours = Math.round(timeDiff % 24); 31 | 32 | // remove hours from the date 33 | timeDiff = Math.floor(timeDiff / 24); 34 | 35 | // the rest of timeDiff is number of days 36 | var days = timeDiff; 37 | 38 | if(days > 0) { 39 | return `${days} days ago` 40 | } else if(hours > 0) { 41 | return `${hours} hours ago` 42 | } else if(minutes > 0) { 43 | return `${minutes} minutes ago` 44 | } else if(seconds > 0) { 45 | return `${seconds} seconds ago` 46 | } else { 47 | return "just now" 48 | } 49 | } 50 | 51 | export default (props) => { 52 | //get prop data 53 | const text = props.text; 54 | const user = props.user; 55 | const uid = props.uid; 56 | const event_type = props.event_type; 57 | const time = formatTime(props.time); 58 | 59 | let message_item =
  • ; 60 | 61 | let renderPlayerImage = function () { 62 | const pic = props.picture; 63 | let isAnonymous = props.anonymous === true; 64 | let playerImage = 65 | User image; 68 | if (isAnonymous) { 69 | return ( 70 | playerImage 71 | ); 72 | } else { 73 | return ( 74 | browserHistory.push(`/profile/${uid}`)}> 76 | {playerImage} 77 | 78 | ); 79 | } 80 | } 81 | 82 | switch(event_type) { 83 | case 'chat-message': 84 | message_item = 85 | 86 |
    87 | {renderPlayerImage()} 88 |
    89 | 90 |
    91 | {user} 92 |

    93 | {text} 94 |

    95 |
    96 |
    {time}
    97 |
    98 |
    ; 99 | break; 100 | default: 101 | message_item = 102 | 103 |
    104 | 105 | {text} 106 | 107 | {time} 108 |
    109 |
    ; 110 | break; 111 | } 112 | return message_item; 113 | } 114 | -------------------------------------------------------------------------------- /server/sockets/listeners/connection.ts: -------------------------------------------------------------------------------- 1 | import Player from '../../game_models/players/Player'; 2 | import Room from '../../game_models/rooms/Room'; 3 | 4 | module.exports = function(io, socket, connection) { 5 | 6 | function disconnect() { 7 | //retrieve the user which has disconnected 8 | let player: Player = connection.getPlayerBySocket(socket); 9 | 10 | //If the player was searching for a game remove them from that game 11 | connection.removePlayerFromQueue(player); 12 | 13 | //get a list of rooms in which the player is in 14 | let rooms: Room [] = connection.getPlayerRoomsByPlayer(player); 15 | 16 | 17 | if(!rooms || !player) { 18 | //TODO error 19 | return; 20 | } 21 | 22 | //remove the player connection 23 | connection.removePlayer(player.playerId); 24 | 25 | rooms.map((room: Room) => { 26 | if(!room.removePlayer(player)) { 27 | //TODO error 28 | return; 29 | } 30 | 31 | if (room.empty() && room.name !== 'Global') { //there are no users in the room 32 | if(connection.removeRoomByName(room.name)) { 33 | //TODO not sure if anything else is needed here 34 | } else { 35 | console.log("could not delete room " + room.name); 36 | } 37 | } else { 38 | if(room.game && room.game.gameStarted == false) { 39 | room.game.removePlayerFromAllSeats(player); 40 | } 41 | //tell everyone that the player has left the room 42 | io.to(room.name).emit('update-room-full', room.getRoomObjFull()); 43 | } 44 | }); 45 | 46 | connection.emitAllRooms(); 47 | } 48 | 49 | //Clients emit this event upon successfully establishing a connection 50 | //The server will track all users 51 | socket.on('connected-user', data => { 52 | if(!data || !data._id || !data.picture) { 53 | return; 54 | } 55 | 56 | let IP = socket.request.connection.remoteAddress; 57 | console.log('client connected IP is ', IP); 58 | 59 | let anonymous = false; 60 | if (data.anonymous === true) { 61 | anonymous = true; 62 | } 63 | 64 | //TODO get player data from database instead of client 65 | let p = new Player( 66 | socket, data._id, data.username, data.picture, 67 | data.social, data.email, 68 | data.standard_ratings, 69 | data.schess_ratings, 70 | data.fourplayer_ratings, 71 | data.crazyhouse_ratings, data.crazyhouse960_ratings, 72 | data.fullhouse_ratings, 73 | IP, 74 | anonymous); 75 | 76 | //check to see if the player is already connected elsewhere 77 | if(connection.duplicateUser(data._id, IP) == true) { 78 | console.log('duplicate login'); 79 | return socket.emit('duplicate-login'); 80 | } 81 | 82 | connection.addPlayer(p); 83 | connection.emitAllRooms(); 84 | }); 85 | 86 | socket.on('update-user', data => { 87 | //Used to update the username on the server when the user adds it 88 | connection.updatePlayer(data); 89 | 90 | // socket.emit('action', {type: 'update-user', payload: data}) 91 | }); 92 | 93 | //Handle error event 94 | socket.on("error", (err) => { 95 | console.log("Caught flash policy server socket error: ") 96 | console.log(err.stack); 97 | }); 98 | 99 | socket.on('logout', data=> {disconnect()}) 100 | 101 | socket.on('disconnect', (reason) => { 102 | disconnect(); 103 | connection.printQueue(); 104 | }); 105 | }; -------------------------------------------------------------------------------- /server/engine/CrazyEngine.ts: -------------------------------------------------------------------------------- 1 | import Room from '../game_models/rooms/Room'; 2 | import Connection from '../sockets/Connection'; 3 | import Game from '../game_models/games/Game'; 4 | import Engine from './Engine'; 5 | const {ab2str} = require('../utils/utils'); 6 | 7 | export default class CrazyEngine extends Engine { 8 | constructor(roomName, increment, connection : Connection, private set_960: boolean) { 9 | super('stockfish_variant', roomName, connection, false); 10 | this.setDepth(15); 11 | this.increment = increment * 1000; // s -> ms 12 | } 13 | onBestMove(data) { 14 | var str = ab2str(data); 15 | if (str.indexOf("uciok") !== -1) { 16 | this.setupOptions(); 17 | } 18 | if(str.indexOf("bestmove") !== -1) { 19 | // console.log("[crazyhouse engine: " + this.roomName+ "] " + str); 20 | let startIndex = str.indexOf("bestmove"); 21 | let from = str.substring(startIndex + 9, startIndex + 11); 22 | let to = str.substring(startIndex + 11, startIndex + 13); 23 | let compMove: any = {}; 24 | if (from.indexOf('@') !== -1) { 25 | let piece = from.charAt(0).toLowerCase(); 26 | from = '@'; 27 | compMove.piece = piece; 28 | } 29 | if (str.substring(startIndex + 13, startIndex + 14) === '=') { 30 | compMove.promotion = str.substring(startIndex + 14, startIndex + 15); 31 | } else { 32 | compMove.promotion = 'q'; 33 | } 34 | compMove.to = to; 35 | compMove.from = from; 36 | 37 | let roomName = this.roomName; 38 | let room: Room = this.connection.getRoomByName(roomName); 39 | if(!room) { 40 | return; 41 | } 42 | let game: Game = room.game; 43 | let move = data.move; 44 | room.makeMove(compMove, Date.now()); 45 | } 46 | } 47 | 48 | setupOptions() { 49 | this.setOption("UCI_Variant", "crazyhouse"); 50 | this.setOption("Skill Level", "7"); 51 | this.setOption("Contempt", "100"); 52 | this.setOption("Move Overhead", "300"); 53 | if (this.set_960) { 54 | this.set960(); 55 | } 56 | //this.setOption("Slow Mover", "600"); 57 | } 58 | 59 | set960() { 60 | this.setOption("UCI_Chess960", "true"); 61 | } 62 | 63 | setPosition(fen) { 64 | if(this.engine) { 65 | this.engine.stdin.write( 66 | "position fen " + fen + "\n" 67 | ); 68 | } 69 | 70 | } 71 | 72 | setTurn(turnColor) { 73 | // console.log("set turn on CrazyEngine"); 74 | } 75 | 76 | setOut(colorOut) { 77 | if(this.engine) { 78 | this.engine.stdin.write("stop"); 79 | } 80 | } 81 | 82 | adjustDepth(timeLeft) { 83 | let depth = this.depth; 84 | return depth; 85 | } 86 | 87 | go(timeLeft, level) { 88 | this.timeLeft = timeLeft; 89 | if (level) { 90 | // console.log("setting comp skill level to", level); 91 | this.setOption("Skill Level", "" + level); 92 | } 93 | let timeString; 94 | if (timeLeft) { 95 | timeString = " wtime "+timeLeft+" btime "+timeLeft+" "; 96 | timeString += "winc "+this.increment+" binc "+this.increment+" "; 97 | } else { 98 | timeString = ""; 99 | } 100 | let goString; 101 | if(this.mode == 0) { 102 | goString = "go" + timeString + "\n"; 103 | // console.log("[engine: "+this.roomName+"]", goString); 104 | if(this.engine) { 105 | this.engine.stdin.write(goString); 106 | } 107 | } else { 108 | goString = "go " + "depth 4" + "\n"; 109 | this.engine.stdin.write(goString); 110 | } 111 | 112 | } 113 | } -------------------------------------------------------------------------------- /server/engine/Engine.ts: -------------------------------------------------------------------------------- 1 | let spawn = require('child_process').spawn; 2 | const path = require('path'); 3 | const {ab2str} = require('../utils/utils'); 4 | 5 | 6 | abstract class Engine { 7 | public turn: string; 8 | public depth: number; 9 | protected engine: any; 10 | protected numOut; 11 | protected mode; 12 | protected timeLeft; 13 | protected increment; 14 | 15 | constructor(public executableName, public roomName, public connection, public windowsExe: Boolean) { 16 | this.roomName = roomName; 17 | this.connection = connection; 18 | 19 | let enginePath = path.join(__dirname, '../../executables/' + executableName); 20 | 21 | try { 22 | this.engine = spawn((windowsExe === true) ? 'wine': enginePath, (windowsExe === true) ? [enginePath] : []); 23 | this.engine.stdin.setEncoding('utf-8'); 24 | this.engine.stdout.on('data', this.onBestMove.bind(this)); 25 | this.engine.stderr.on("data", (err) => { 26 | console.log("Child process output error: " + err); 27 | }); 28 | this.engine.on('error', function(err) { 29 | console.log(err); 30 | }); 31 | } catch (err) { 32 | console.log(err); 33 | } 34 | 35 | this.numOut = 0; 36 | this.mode = 0; 37 | if (windowsExe === false) {this.sendUci();} 38 | } 39 | 40 | abstract go(timeLeft: number, depth: number, engineMatch: boolean): void; 41 | abstract adjustDepth(timeLeft: number, level: number): void; 42 | abstract onBestMove(data: any): void; 43 | abstract setPosition(fen): void; 44 | abstract setTurn(turnColor): void; 45 | abstract setOut(colorOut): void; 46 | 47 | setMode(mode) { 48 | this.mode = mode; 49 | } 50 | 51 | colorToTurnNumber(color) { 52 | if (!color) 53 | return '0'; 54 | color = color.charAt(0); 55 | let turn; 56 | switch(color) { 57 | case 'w': 58 | turn = '0'; 59 | break; 60 | case 'b': 61 | turn = '1'; 62 | break; 63 | case 'g': 64 | turn = '2'; 65 | break; 66 | case 'r': 67 | turn = '3'; 68 | break; 69 | default: 70 | turn = '0'; 71 | } 72 | return turn; 73 | } 74 | 75 | public setDepth(depth): void { 76 | this.depth = depth; 77 | } 78 | 79 | sendUci() { 80 | if(this.engine && this.engine.stdin) { 81 | this.engine.stdin.write("uci\n"); 82 | } 83 | } 84 | 85 | setOption(name, value) { 86 | if(this.engine && this.engine.stdin) { 87 | this.engine.stdin.write( 88 | "setoption name " + name + " value " + value + "\n" 89 | ); 90 | } 91 | } 92 | 93 | setWinboardForceMode() { 94 | if(this.engine && this.engine.stdin) { 95 | this.engine.stdin.write( 96 | "force\n" 97 | ); 98 | } 99 | } 100 | 101 | //Set the time for search on winboard protocol (in seconds) 102 | setWinboardTime(time) { 103 | if(this.engine && this.engine.stdin) { 104 | this.engine.stdin.write( 105 | "st " + time + "\r\n" 106 | ); 107 | } 108 | } 109 | 110 | setTurnWinboard(turn) { 111 | if(this.engine && this.engine.stdin) { 112 | if (turn === 'w') { turn = 'white'} 113 | if (turn === 'b') { turn = 'black'} 114 | this.engine.stdin.write( 115 | turn + "\n" 116 | ); 117 | } 118 | } 119 | 120 | kill() { 121 | // console.log("killing engine", this.roomName); 122 | if(this.engine && this.engine.stdin) { 123 | this.engine.stdin.pause(); 124 | this.engine.kill(); 125 | } 126 | 127 | this.engine = null; 128 | } 129 | } 130 | 131 | export default Engine; -------------------------------------------------------------------------------- /server/game_models/games/Standard.ts: -------------------------------------------------------------------------------- 1 | import Game from './Game'; 2 | const {Chess} = require('chess.js'); 3 | const {User} = require('../../models/user'); 4 | const {StandardGame} = require('../../models/standard'); 5 | const Elo = require('elo-js'); 6 | import Player from '../players/Player'; 7 | import TwoEngine from '../../engine/TwoEngine'; 8 | const Notifications = require('react-notification-system-redux'); 9 | import Connection from '../../sockets/Connection'; 10 | import Room from '../rooms/Room'; 11 | import {DrawMessage, WinnerMessage} from '../rooms/Message'; 12 | 13 | export default class Standard extends Game { 14 | gameType: string = 'standard'; 15 | gameRulesObj: any = new Chess(); 16 | numPlayers: number = 2; 17 | io: any; 18 | times: any = { 19 | w: 0, 20 | b: 0 21 | }; 22 | time: any; 23 | connection: Connection; 24 | ratings_type: string = 'standard_ratings'; 25 | gameClassDB: any = StandardGame; 26 | 27 | constructor(io: Object, roomName:string, time: any, connection: Connection) { 28 | super(); 29 | this.io = io; 30 | this.gameRulesObj = new Chess(); 31 | this.roomName = roomName; 32 | this.time = time; 33 | let initialTime = time.value * 60 * 1000; 34 | this.times = { 35 | w: initialTime, 36 | b: initialTime 37 | }; 38 | this.connection = connection; 39 | } 40 | 41 | 42 | setPlayerResignByPlayerObj(player: Player) { 43 | if(this.white && this.white.playerId === player.playerId) { 44 | this.white.alive = false; 45 | } 46 | 47 | if(this.black && this.black.playerId === player.playerId) { 48 | this.black.alive = false; 49 | } 50 | } 51 | 52 | getGame() { 53 | return { 54 | numPlayers: this.numPlayers, 55 | gameType: this.gameType, 56 | fen: this.gameRulesObj.fen(), 57 | pgn: this.getMoveHistory(), 58 | move: this._lastMove, 59 | white: (this.white) ? this.white.getPlayer():false, 60 | black: (this.black) ? this.black.getPlayer():false, 61 | turn: this.gameRulesObj.turn(), 62 | gameStarted: this.gameStarted, 63 | }; 64 | } 65 | 66 | move() { 67 | 68 | } 69 | 70 | removePlayerFromAllSeats(player: Player) { 71 | if(this.white && this.white.playerId === player.playerId) { 72 | this.white = null; 73 | } 74 | 75 | if(this.black && this.black.playerId === player.playerId) { 76 | this.black = null; 77 | } 78 | } 79 | 80 | removePlayer(color: string) { 81 | switch(color.charAt(0)) { 82 | case 'w': 83 | this.white = null; 84 | break; 85 | case 'b': 86 | this.black = null; 87 | break; 88 | } 89 | return true; 90 | } 91 | 92 | gameReady(): boolean { 93 | return ( 94 | this.white !== null && 95 | this.black !== null); 96 | } 97 | 98 | outColor(): string { return null; } 99 | 100 | newEngineInstance(roomName: string, io: any) { 101 | this.engineInstance = new TwoEngine(roomName, this.time.increment, this.connection); 102 | } 103 | 104 | startGame() { 105 | this.gameStarted = true; 106 | this.white.alive = true; 107 | this.black.alive = true; 108 | this.resetClocks(); 109 | this.gameRulesObj = new Chess(); 110 | this.moveHistory = []; 111 | } 112 | 113 | 114 | setPlayerOutByColor(color: string) { 115 | let playerOut = null; 116 | switch(color.charAt(0)) { 117 | case 'w': 118 | this.white.alive = false; 119 | playerOut = this.white; 120 | break; 121 | case 'b': 122 | this.black.alive = false; 123 | playerOut = this.black; 124 | break; 125 | } 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /server/controllers/games.js: -------------------------------------------------------------------------------- 1 | const _ = require('lodash'); 2 | const {StandardGame} = require('../models/standard'); 3 | import {SChessDB} from '../models/schess'; 4 | const {CrazyhouseGame} = require('../models/crazyhouse'); 5 | const {Crazyhouse960Game} = require('../models/crazyhouse960'); 6 | const {FourGameDB} = require('../models/fourgame'); 7 | const {User} = require('../models/user'); 8 | const {ObjectID} = require('mongodb'); 9 | const Async = require('async'); 10 | 11 | exports.getRecentGames = (req, res, next) => { 12 | const id = req.params.id; 13 | 14 | if(!ObjectID.isValid(id)) { 15 | return res.status(404).send(); 16 | } 17 | 18 | let recentGames = { 19 | standard: function(cb) { 20 | //get last 5 standard games 21 | StandardGame.find({ $or : [ { "white.user_id": ObjectID(id)}, { "black.user_id": ObjectID(id) } ] } ) 22 | .populate('black.user_id') 23 | .populate('white.user_id') 24 | .sort({$natural: -1}) 25 | .limit(5) 26 | .then((games) => { 27 | cb(null, games); 28 | }) 29 | .catch((e) => { 30 | cb(e); 31 | }); 32 | }, 33 | four_player: function(cb) { 34 | //get last 5 schess games 35 | FourGameDB.find({ $or : [ 36 | { "white.user_id": ObjectID(id) }, 37 | { "black.user_id": ObjectID(id) }, 38 | { "gold.user_id": ObjectID(id) }, 39 | { "red.user_id": ObjectID(id) }, 40 | ]}) 41 | .populate('black.user_id') 42 | .populate('white.user_id') 43 | .populate('gold.user_id') 44 | .populate('red.user_id') 45 | .sort({$natural: -1}) 46 | .limit(5) 47 | .then((games) => { 48 | cb(null, games); 49 | }) 50 | .catch((e) => { 51 | cb(e); 52 | }); 53 | }, 54 | schess: function(cb) { 55 | //get last 5 schess games 56 | SChessDB.find({ $or : [ { "white.user_id": ObjectID(id)}, { "black.user_id": ObjectID(id) } ] } ) 57 | .populate('black.user_id') 58 | .populate('white.user_id') 59 | .sort({$natural: -1}) 60 | .limit(5) 61 | .then((games) => { 62 | cb(null, games); 63 | }) 64 | .catch((e) => { 65 | cb(e); 66 | }); 67 | }, 68 | crazyhouse: function(cb) { 69 | //get last 5 crazyhouse games 70 | CrazyhouseGame.find({ $or : [ { "white.user_id": ObjectID(id)}, { "black.user_id": ObjectID(id) } ] } ) 71 | .populate('black.user_id') 72 | .populate('white.user_id') 73 | .sort({$natural: -1}) 74 | .limit(5) 75 | .then((games) => { 76 | cb(null, games); 77 | }) 78 | .catch((e) => { 79 | cb(e); 80 | }); 81 | }, 82 | crazyhouse960: function(cb) { 83 | //get last 5 crazyhouse games 84 | Crazyhouse960Game.find({ $or : [ { "white.user_id": ObjectID(id)}, { "black.user_id": ObjectID(id) } ] } ) 85 | .populate('black.user_id') 86 | .populate('white.user_id') 87 | .sort({$natural: -1}) 88 | .limit(5) 89 | .then((games) => { 90 | cb(null, games); 91 | }) 92 | .catch((e) => { 93 | cb(e); 94 | }); 95 | }, 96 | 97 | } 98 | 99 | Async.parallel (recentGames, function (err, results) { 100 | 101 | if (err) { console.log(err); res.status(400).send(err);} 102 | 103 | //results holds the leaderboard object 104 | res.send(results); 105 | 106 | }); 107 | } 108 | 109 | -------------------------------------------------------------------------------- /server/game_models/games/FullhouseChess.ts: -------------------------------------------------------------------------------- 1 | import Game from './Game'; 2 | import {FullhouseChess as FullhouseChessGame} from '../../../common/fullhouse-chess'; 3 | const {User} = require('../../models/user'); 4 | const {FullhouseChessDB} = require('../../models/fullhouse-chess'); 5 | const Elo = require('elo-js'); 6 | import Player from '../players/Player'; 7 | import TwoEngine from '../../engine/TwoEngine'; 8 | const Notifications = require('react-notification-system-redux'); 9 | import Connection from '../../sockets/Connection'; 10 | import Room from '../rooms/Room'; 11 | import {DrawMessage, WinnerMessage} from '../rooms/Message'; 12 | 13 | export default class FullhouseChess extends Game { 14 | gameType: string = 'fullhouse-chess'; 15 | gameRulesObj: any = FullhouseChessGame(); 16 | numPlayers: number = 2; 17 | io: any; 18 | times: any = { 19 | w: 0, 20 | b: 0 21 | }; 22 | time: any; 23 | connection: Connection; 24 | ratings_type: string = 'fullhouse_ratings'; 25 | gameClassDB: any = FullhouseChessDB; 26 | 27 | constructor(io: Object, roomName:string, time: any, connection: Connection) { 28 | super(); 29 | this.io = io; 30 | this.gameRulesObj = FullhouseChessGame(); 31 | this.roomName = roomName; 32 | this.time = time; 33 | let initialTime = time.value * 60 * 1000; 34 | this.times = { 35 | w: initialTime, 36 | b: initialTime 37 | }; 38 | this.connection = connection; 39 | } 40 | 41 | setPlayerResignByPlayerObj(player: Player) { 42 | if(this.white && this.white.playerId === player.playerId) { 43 | this.white.alive = false; 44 | } 45 | 46 | if(this.black && this.black.playerId === player.playerId) { 47 | this.black.alive = false; 48 | } 49 | } 50 | 51 | move() { 52 | 53 | } 54 | 55 | getGame() { 56 | return { 57 | numPlayers: this.numPlayers, 58 | gameType: this.gameType, 59 | fen: this.gameRulesObj.fen(), 60 | pgn: this.getMoveHistory(), 61 | move: this._lastMove, 62 | white: (this.white) ? this.white.getPlayer():false, 63 | black: (this.black) ? this.black.getPlayer():false, 64 | turn: this.gameRulesObj.turn(), 65 | gameStarted: this.gameStarted, 66 | }; 67 | } 68 | 69 | 70 | removePlayerFromAllSeats(player: Player) { 71 | if(this.white && this.white.playerId === player.playerId) { 72 | this.white = null; 73 | } 74 | 75 | if(this.black && this.black.playerId === player.playerId) { 76 | this.black = null; 77 | } 78 | } 79 | 80 | removePlayer(color: string) { 81 | switch(color.charAt(0)) { 82 | case 'w': 83 | this.white = null; 84 | break; 85 | case 'b': 86 | this.black = null; 87 | break; 88 | } 89 | return true; 90 | } 91 | 92 | gameReady(): boolean { 93 | return ( 94 | this.white !== null && 95 | this.black !== null); 96 | } 97 | 98 | outColor(): string { return null; } 99 | 100 | newEngineInstance(roomName: string, io: any) { 101 | // this.engineInstance = new TwoEngine(roomName, this.time.increment, this.connection); 102 | } 103 | 104 | startGame() { 105 | this.gameStarted = true; 106 | this.white.alive = true; 107 | this.black.alive = true; 108 | this.resetClocks(); 109 | this.gameRulesObj = FullhouseChessGame(); 110 | this.moveHistory = []; 111 | } 112 | 113 | 114 | setPlayerOutByColor(color: string) { 115 | let playerOut = null; 116 | switch(color.charAt(0)) { 117 | case 'w': 118 | this.white.alive = false; 119 | playerOut = this.white; 120 | break; 121 | case 'b': 122 | this.black.alive = false; 123 | playerOut = this.black; 124 | break; 125 | } 126 | } 127 | 128 | } -------------------------------------------------------------------------------- /server/sockets/listeners/matchmaking.ts: -------------------------------------------------------------------------------- 1 | import Player from '../../game_models/players/Player'; 2 | import Room from '../../game_models/rooms/Room'; 3 | import QueueItem from '../../game_models/matchmaking/QueueItem'; 4 | 5 | module.exports = function(io, socket, connection) { 6 | 7 | //The user wants to stop being paried 8 | socket.on('stop-pairing', () => { 9 | // Get the player that is requesting to be paried 10 | let player: Player = connection.getPlayerBySocket(socket); 11 | if(!player) { 12 | return; 13 | } 14 | connection.removePlayerFromQueue(player); 15 | 16 | player.socket.emit('stopped-pairing'); 17 | connection.printQueue(); 18 | }); 19 | 20 | //the user is requesting to get paired at a certain time/inc 21 | socket.on('pair-me', (data) => { 22 | let timeControl : number = data.timeControl; 23 | let increment : number = data.increment; 24 | let gameType : string = data.gameType; 25 | 26 | // Get the player that is requesting to be paried 27 | let player: Player = connection.getPlayerBySocket(socket); 28 | let opponent: Player = null; 29 | if(!player) { 30 | return; 31 | } 32 | // remove any prior instances of this player from the queue 33 | connection.removePlayerFromQueue(player); 34 | 35 | // Get the current Queue 36 | let queue = connection.queue; 37 | 38 | // Search for an opponent (FIFO) 39 | for(let i = 0; i < queue.length; i++) { 40 | let entry: QueueItem = queue[i]; 41 | 42 | //Currently all that is checked is if the timecontrol and the 43 | // gametype match up 44 | if( entry.timeControl === timeControl 45 | && entry.timeIncrement === increment 46 | && entry.gameType === gameType) { 47 | 48 | //Remove entry from queue 49 | connection.removeQueueEntryAtIndex(i); 50 | 51 | opponent = entry.player; 52 | break; 53 | } 54 | } 55 | 56 | let timeObj = { 57 | value: timeControl, 58 | increment: increment 59 | }; 60 | 61 | if(opponent === null) { 62 | //No one was found 63 | let newEntry: QueueItem = new QueueItem(player, gameType, timeControl, increment); 64 | queue.push(newEntry); 65 | player.socket.emit('in-queue', data); 66 | } else { 67 | let roomObj = { 68 | name: player.username + ' vs ' + opponent.username, 69 | private: false, 70 | voiceChat: false, 71 | maxPlayers: 10000, 72 | roomMode: 'match', 73 | }; 74 | //An opponent was found, start the game 75 | let room: Room = connection.createNewRoom( 76 | player.username + ' vs ' + opponent.username, 77 | data.gameType, timeObj, roomObj); 78 | room.addAllowedPlayerID(player.playerId); 79 | room.addAllowedPlayerID(opponent.playerId); 80 | if(!room) { 81 | return; 82 | } 83 | room.addPlayer(player); 84 | room.addPlayer(opponent); 85 | connection.emitAllRooms(); 86 | 87 | let color: string = 'w'; 88 | room.game.addPlayer(player, color); 89 | let timeValue = room.time.value * 60 * 1000; 90 | room.game.setColorTime(color, timeValue); 91 | 92 | color = 'b'; 93 | room.game.addPlayer(opponent, color); 94 | timeValue = room.time.value * 60 * 1000; 95 | room.game.setColorTime(color, timeValue); 96 | 97 | if (room.gameReady()) { 98 | // start the game if all players are seated 99 | room.startGame(connection); 100 | } 101 | io.to(room.name).emit('update-room-full', room.getRoomObjFull()); 102 | io.to(room.name).emit('matchmaking-complete', room.name); 103 | } 104 | connection.printQueue(); 105 | }); 106 | }; -------------------------------------------------------------------------------- /src/containers/room_viewer.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import { connect } from 'react-redux'; 3 | import ExistingRoomList from './existing_room_list'; 4 | import Room from '../components/room/room'; 5 | import {mapObject} from '../utils/'; 6 | import { selectedRoom, joinRoom, leaveRoom, updateLiveUser } from '../actions'; 7 | import { closeAnalysisRoom } from '../actions/room'; 8 | 9 | import {Tabs, Tab, TabContainer, TabContent, TabPane} from 'react-bootstrap'; 10 | 11 | class RoomViewer extends Component { 12 | 13 | constructor(props) { 14 | super(props); 15 | } 16 | 17 | componentWillMount() { 18 | 19 | if(this.props.connection.status) { 20 | if(!this.props.activeProfile._id) { 21 | this.props.updateLiveUser(this.props.profile); 22 | } 23 | } 24 | } 25 | 26 | componentDidMount() { 27 | this.removeHrefs(); 28 | } 29 | 30 | componentDidUpdate(prevProps) { 31 | this.removeHrefs(); 32 | 33 | } 34 | 35 | onSelectTab(chatName, event) { 36 | event.preventDefault(); 37 | event.stopPropagation(); 38 | this.props.selectedRoom(chatName); 39 | } 40 | 41 | onCloseChatTab(chatName, event, chatValue) { 42 | event.preventDefault(); 43 | event.stopPropagation(); 44 | if(chatValue.mode === 'analysis') { 45 | this.props.closeAnalysisRoom(chatName); 46 | } else { 47 | this.props.leaveRoom(chatName); 48 | } 49 | 50 | if (this.props.activeThread === chatName) { 51 | let openThreadNames = 52 | Object.keys(this.props.openThreads).filter(name => name !== chatName); 53 | let numThreads = openThreadNames.length; 54 | if (numThreads > 0) { 55 | try { 56 | this.props.selectedRoom(openThreadNames[numThreads-1]); 57 | } catch (e) { 58 | this.props.selectedRoom(200); 59 | } 60 | } else { 61 | this.props.selectedRoom(200); 62 | } 63 | } 64 | } 65 | 66 | // remove hrefs from tab links so it doesn't show 67 | // the useless '#' link on hover 68 | removeHrefs() { 69 | let tabsContainer = $("ul[role='tablist']") 70 | let tabs = tabsContainer.find('li a'); 71 | tabs.removeAttr('href'); 72 | } 73 | 74 | renderNavTab(chats, active) { 75 | return mapObject(chats, (key, chat) => { 76 | let title = 77 | {key} 80 | ; 81 | return ( 82 | 83 |
    84 | 85 |
    86 |
    87 | ); 88 | }); 89 | } 90 | 91 | render() { 92 | if(!this.props.profile || !this.props.profile.username) { 93 | return
    94 |
    95 | } 96 | 97 | let {activeThread, openThreads} = this.props; 98 | 99 | if(!activeThread || !openThreads) { 100 | return
    Loading...
    ; 101 | } 102 | 103 | return ( 104 | 109 | {this.gamesTab = tab;}}> 113 | 114 | 115 | {this.renderNavTab(openThreads, activeThread)} 116 | 117 | ); 118 | } 119 | } 120 | 121 | function mapStateToProps(state) { 122 | return { 123 | connection: state.connection, 124 | activeThread: state.activeThread, 125 | openThreads: state.openThreads, 126 | profile: state.auth.profile, 127 | activeProfile: state.currentProfile, 128 | }; 129 | } 130 | 131 | export default connect(mapStateToProps, {selectedRoom, joinRoom, leaveRoom, updateLiveUser, closeAnalysisRoom}) (RoomViewer); 132 | -------------------------------------------------------------------------------- /server/engine/FourEngine.ts: -------------------------------------------------------------------------------- 1 | import Engine from './Engine'; 2 | import Room from '../game_models/rooms/Room'; 3 | import Connection from '../sockets/Connection'; 4 | import Game from '../game_models/games/Game'; 5 | 6 | const {ab2str} = require('../utils/utils'); 7 | 8 | export default class FourEngine extends Engine { 9 | constructor(roomName: string, connection: Connection) { 10 | super('fourengine', roomName, connection, false); 11 | this.setDepth(6); 12 | this.turn = 'w'; 13 | } 14 | onBestMove(data) { 15 | var str = ab2str(data); 16 | if(str.indexOf("bestmove") !== -1) { 17 | let score = null, pv = null; 18 | 19 | let turn = null; 20 | 21 | switch(this.turn) { 22 | case 'w': 23 | turn = 'white'; 24 | break; 25 | case 'b': 26 | turn = 'black'; 27 | break; 28 | case 'g': 29 | turn = 'gold'; 30 | break; 31 | case 'r': 32 | turn = 'red'; 33 | break; 34 | } 35 | 36 | // move format: e2-e3 37 | let moveMatch = str.match(/bestmove ([a-n]\d+\-[a-n]\d+)/i); 38 | if (moveMatch == null || typeof moveMatch[1] === "undefined") 39 | return; 40 | let moveString = moveMatch[1]; 41 | let moveSplit = moveString.split('-'); 42 | let compMove = { 43 | from: moveSplit[0], 44 | to: moveSplit[1], 45 | promotion: 'q' 46 | }; 47 | 48 | // console.log("FourEngine move:", compMove); 49 | 50 | let roomName = this.roomName; 51 | let room: Room = this.connection.getRoomByName(roomName); 52 | if(!room) { 53 | return; 54 | } 55 | let game: Game = room.game; 56 | let move = data.move; 57 | setTimeout(() => { 58 | room.makeMove(compMove, Date.now()); 59 | }, 400); 60 | 61 | 62 | return; 63 | } 64 | } 65 | 66 | setPosition(fen) { 67 | if(this.engine) { 68 | this.engine.stdin.write( 69 | "position fen " + fen.split('-')[0] + "\n" 70 | ); 71 | } 72 | 73 | } 74 | 75 | setTurn(turnColor) { 76 | this.turn = turnColor; 77 | let turn = this.colorToTurnNumber(turnColor); 78 | if(this.engine) { 79 | this.engine.stdin.write("turn "+ turn + "\n"); 80 | } 81 | } 82 | 83 | setOut(colorOut) { 84 | let out = this.colorToTurnNumber(colorOut); 85 | if(this.engine) { 86 | this.engine.stdin.write("out " + out + "\n"); 87 | } 88 | this.numOut += 1; 89 | } 90 | 91 | adjustDepth(timeLeft, level) { 92 | let depth = this.depth; 93 | switch(level) { 94 | case 1: 95 | depth = 3; 96 | break; 97 | case 5: 98 | depth = 4; 99 | break; 100 | case 10: 101 | depth = 4; 102 | break; 103 | case 15: 104 | depth = 5; 105 | break; 106 | case 20: 107 | depth = 6; 108 | break; 109 | } 110 | if (timeLeft < 1000) { // 1 second 111 | depth = Math.min(1, depth); 112 | } else if (timeLeft < 2000) { // 2 seconds 113 | depth = Math.min(2, depth); 114 | } else if (timeLeft < 3000) { // 3 seconds 115 | depth = Math.min(3, depth); 116 | } else if (timeLeft < 10000) { // 10 seconds 117 | depth = Math.min(4, depth); 118 | } else if (timeLeft < 120000) { // 2 mins 119 | depth = Math.min(5, depth); 120 | } 121 | return depth; 122 | } 123 | 124 | go(timeLeft, level) { 125 | this.timeLeft = timeLeft; 126 | let depth = this.depth; 127 | depth = this.adjustDepth(timeLeft, level); 128 | if(this.mode == 0) { 129 | // console.log("[FourEngine "+this.roomName+"]", "skill level:", level, "depth:", depth); 130 | if (this.engine) this.engine.stdin.write("go depth " + depth + "\n"); 131 | } else { 132 | let goString = "go " + "depth 4" + "\n"; 133 | if(this.engine) this.engine.stdin.write(goString); 134 | } 135 | 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /server/services/passport.js: -------------------------------------------------------------------------------- 1 | const passport = require('passport'); 2 | const JwtStrategy = require('passport-jwt').Strategy; 3 | const ExtractJwt = require('passport-jwt').ExtractJwt; 4 | const FacebookTokenStrategy = require('passport-facebook-token'); 5 | const GooglePlusTokenStrategy = require('passport-google-plus-token'); 6 | const {User} = require('../models/user'); 7 | const config = require('../../config/config'); 8 | const LocalStrategy = require('passport-local'); 9 | 10 | const localOptions = { 11 | usernameField: 'email' 12 | } 13 | 14 | //Login strategy, verifies auth token. 15 | const localLogin = new LocalStrategy(localOptions, (email, password, done) => { 16 | User.findOne({email}).select('+password') 17 | .then((user) => { 18 | if(!user) { 19 | done(null, false); 20 | } 21 | 22 | user.comparePassword(password, (err, isMatch) => { 23 | if(err) { 24 | return done(err); 25 | } 26 | if(!isMatch) { 27 | return done(null, false); 28 | } 29 | 30 | return done(null, user); 31 | }); 32 | 33 | }).catch((e) => { 34 | done(e); 35 | }); 36 | }); 37 | 38 | //options for JWT strategy 39 | const jwtOptions= { 40 | jwtFromRequest: ExtractJwt.fromHeader('x-auth'), 41 | secretOrKey: config.secret 42 | }; 43 | 44 | const jwtLogin = new JwtStrategy(jwtOptions, function(payload, done) { 45 | 46 | User.findById(payload.sub, function(err, user) { 47 | if(err) { 48 | return done(err, false); 49 | } 50 | 51 | if(user) { 52 | done(null, user); 53 | } else { 54 | done(null, false); 55 | } 56 | }); 57 | }); 58 | 59 | const { clientID, clientSecret, callbackURL, profileFields } = config.facebookAuth; 60 | 61 | const FBLogin = new FacebookTokenStrategy({ clientID, clientSecret, profileFields }, 62 | function(accessToken, refreshToken, profile, done) { 63 | User.findOne({'socialProvider.id': profile.id}, (err, user) => { 64 | if(err) { 65 | return done() 66 | } 67 | if(user) { 68 | return done(null, user); 69 | } 70 | else { 71 | let newUser = new User(); 72 | 73 | newUser.socialProvider.name = 'facebook', 74 | newUser.socialProvider.id = profile.id; 75 | newUser.social = true; 76 | newUser.name = profile.name.givenName + ' ' + profile.name.familyName; 77 | newUser.email = profile.emails[0].value; 78 | newUser.picture = profile._json.picture.data.url; 79 | newUser.social.token = accessToken; 80 | 81 | newUser.save((err) => { 82 | if(err) { 83 | console.log(err) 84 | } 85 | return done(null, newUser); 86 | }); 87 | } 88 | }) 89 | } 90 | ); 91 | 92 | const { GoogleClientID, GoogleClientSecret } = config.googleAuth; 93 | 94 | const GoogleLogin = new GooglePlusTokenStrategy({ 95 | clientID: GoogleClientID, 96 | clientSecret: GoogleClientSecret, 97 | passReqToCallback: true }, 98 | function(req, accessToken, refreshToken, profile, done) { 99 | User.findOne({'socialProvider.id': profile.id}, (err, user) => { 100 | if(err) { 101 | return done() 102 | } 103 | if(user) { 104 | return done(null, user); 105 | } 106 | else { 107 | let newUser = new User(); 108 | 109 | newUser.socialProvider.name = profile.provider 110 | newUser.socialProvider.id = profile.id; 111 | newUser.social = true; 112 | newUser.name = profile.name.givenName + ' ' + profile.name.familyName ; 113 | newUser.email = profile.emails[0].value; 114 | newUser.picture = profile._json.image.url; 115 | newUser.social.token = accessToken; 116 | 117 | newUser.save((err) => { 118 | if(err) { 119 | console.log(err) 120 | } 121 | return done(null, newUser); 122 | }); 123 | } 124 | }) 125 | } 126 | ); 127 | 128 | passport.serializeUser(function(user, cb) { 129 | cb(null, user); 130 | }); 131 | 132 | passport.deserializeUser(function(obj, cb) { 133 | cb(null, obj); 134 | }); 135 | 136 | //tell passport to use Strategies 137 | passport.use(jwtLogin); 138 | passport.use(localLogin); 139 | passport.use(FBLogin); 140 | passport.use(GoogleLogin); 141 | -------------------------------------------------------------------------------- /src/actions/user.js: -------------------------------------------------------------------------------- 1 | import axios from 'axios' 2 | import Notifications from 'react-notification-system-redux'; 3 | import {generateTokenHeader} from '../utils/index'; 4 | import { 5 | ROOT_URL, 6 | VIEW_PROFILE, 7 | VIEW_LEADERBOARD, 8 | RECENT_GAMES, 9 | PLAYERLIST_SUCCESS, 10 | PLAYERLIST_DONE 11 | } from './types'; 12 | 13 | export function clearRecentGames() { 14 | return { 15 | type: 'CLEAR_RECENT_GAMES' 16 | }; 17 | } 18 | 19 | export function clearCurrentProfile() { 20 | return { 21 | type: 'CLEAR_CURRENT_PROFILE' 22 | } 23 | } 24 | 25 | export function getRecentGames(profileId) { 26 | return (dispatch) => { 27 | const tokenHeader = generateTokenHeader(); 28 | axios.get(`${ROOT_URL}/api/games/recentgames/${profileId}`, tokenHeader) 29 | .then((res) => { 30 | dispatch({type: RECENT_GAMES, payload: res.data}); 31 | }) 32 | .catch(function (error) { 33 | const notificationOpts = { 34 | // uid: 'once-please', // you can specify your own uid if required 35 | title: 'Error could not get recent Games!', 36 | message: `${error}`, 37 | position: 'tc', 38 | autoDismiss: 0 39 | }; 40 | 41 | //Show failed log in 42 | dispatch( 43 | Notifications.error(notificationOpts) 44 | ); 45 | }); 46 | }; 47 | } 48 | 49 | export function getUserProfile (id) { 50 | return (dispatch) => { 51 | const tokenHeader = generateTokenHeader(); 52 | axios.get(`${ROOT_URL}/api/users/${id}`, tokenHeader) 53 | .then((res) => { 54 | dispatch({type: VIEW_PROFILE, payload: res.data}); 55 | }) 56 | .catch(function (error) { 57 | const notificationOpts = { 58 | // uid: 'once-please', // you can specify your own uid if required 59 | title: 'Error could not get profile!', 60 | message: '', 61 | position: 'tc', 62 | autoDismiss: 0 63 | }; 64 | 65 | //Show failed log in 66 | dispatch( 67 | Notifications.error(notificationOpts) 68 | ); 69 | }); 70 | }; 71 | } 72 | 73 | export function getLeaderboard () { 74 | return (dispatch) => { 75 | const tokenHeader = generateTokenHeader(); 76 | axios.get(`${ROOT_URL}/api/leaderboard`, tokenHeader) 77 | .then((res) => { 78 | dispatch({type: VIEW_LEADERBOARD, payload: res.data}); 79 | }) 80 | .catch(function (error) { 81 | const notificationOpts = { 82 | // uid: 'once-please', // you can specify your own uid if required 83 | title: 'Error could not get leaderboard!', 84 | message: `${error}`, 85 | position: 'tc', 86 | autoDismiss: 0 87 | }; 88 | 89 | //Show failed log in 90 | dispatch( 91 | Notifications.error(notificationOpts) 92 | ); 93 | }); 94 | }; 95 | } 96 | 97 | export function getPlayerList (n) { 98 | return (dispatch) => { 99 | const tokenHeader = generateTokenHeader(); 100 | axios.get(`${ROOT_URL}/api/playerlist/${n}`, tokenHeader) 101 | .then((res) => { 102 | if(res.data.length < 12) { 103 | dispatch({type: PLAYERLIST_DONE}); 104 | } 105 | return dispatch({type: PLAYERLIST_SUCCESS, payload: res.data}); 106 | 107 | }) 108 | .catch(function (error) { 109 | const notificationOpts = { 110 | // uid: 'once-please', // you can specify your own uid if required 111 | title: 'Error could not get players!', 112 | message: `${error}`, 113 | position: 'tc', 114 | autoDismiss: 0 115 | }; 116 | 117 | //Show failed log in 118 | dispatch( 119 | Notifications.error(notificationOpts) 120 | ); 121 | }); 122 | }; 123 | } 124 | 125 | export function userSearch(query) { 126 | return dispatch => { 127 | const tokenHeader = generateTokenHeader(); 128 | axios.get(`${ROOT_URL}/api/users/search/${query}`, tokenHeader) 129 | .then(res => { 130 | return dispatch({type: 'user-search', payload: res.data}); 131 | }) 132 | .catch(error => { 133 | console.log("error doing userSearch:", error); 134 | }); 135 | } 136 | } 137 | 138 | export function selectedChallengeId(userId) { 139 | return { 140 | type: 'challenged-player-id', 141 | payload: userId, 142 | } 143 | } -------------------------------------------------------------------------------- /server/game_models/games/SChess.ts: -------------------------------------------------------------------------------- 1 | import Game from './Game'; 2 | import {SChess as SChessGame} from 'schess.js'; 3 | const {User} = require('../../models/user'); 4 | const {SChessDB} = require('../../models/schess'); 5 | const Elo = require('elo-js'); 6 | import Player from '../players/Player'; 7 | import TwoEngine from '../../engine/TwoEngine'; 8 | import SChessEngine from '../../engine/SChessEngine'; 9 | const Notifications = require('react-notification-system-redux'); 10 | import Connection from '../../sockets/Connection'; 11 | import Room from '../rooms/Room'; 12 | import {DrawMessage, WinnerMessage} from '../rooms/Message'; 13 | 14 | function getTimeTypeForTimeControl (time) { 15 | let tcIndex; 16 | //this time estimate is based on an estimated game length of 35 moves 17 | let totalTimeMs = (time.value * 60 * 1000) + (35 * time.increment * 1000); 18 | 19 | //Two player cutoff times 20 | let twoMins = 120000; //two minutes in ms 21 | let eightMins = 480000; 22 | let fifteenMins = 900000; 23 | 24 | //four player cutoff times 25 | let fourMins = 240000; 26 | let twelveMins = 720000; 27 | let twentyMins = 12000000; 28 | 29 | if (totalTimeMs <= twoMins) { 30 | //bullet 31 | tcIndex = 'bullet'; 32 | } else if (totalTimeMs <= eightMins) { 33 | //blitz 34 | tcIndex = 'blitz'; 35 | } else if (totalTimeMs <= fifteenMins) { 36 | //rapid 37 | tcIndex = 'rapid'; 38 | } else { 39 | //classical 40 | tcIndex = 'classic'; 41 | } 42 | return tcIndex; 43 | } 44 | 45 | export default class SChess extends Game { 46 | gameType: string = 'schess'; 47 | gameRulesObj: any = new SChessGame(); 48 | numPlayers: number = 2; 49 | io: any; 50 | times: any = { 51 | w: 0, 52 | b: 0 53 | }; 54 | time: any; 55 | connection: Connection; 56 | ratings_type: string = "schess_ratings"; 57 | gameClassDB: any = SChessDB; 58 | 59 | constructor(io: Object, roomName:string, time: any, connection: Connection) { 60 | super(); 61 | this.io = io; 62 | this.gameRulesObj = new SChessGame(); 63 | this.roomName = roomName; 64 | this.time = time; 65 | let initialTime = time.value * 60 * 1000; 66 | this.times = { 67 | w: initialTime, 68 | b: initialTime 69 | }; 70 | this.connection = connection; 71 | } 72 | 73 | 74 | setPlayerResignByPlayerObj(player: Player) { 75 | if(this.white && this.white.playerId === player.playerId) { 76 | this.white.alive = false; 77 | } 78 | 79 | if(this.black && this.black.playerId === player.playerId) { 80 | this.black.alive = false; 81 | } 82 | } 83 | 84 | getGame() { 85 | return { 86 | numPlayers: this.numPlayers, 87 | gameType: this.gameType, 88 | fen: this.gameRulesObj.fen(), 89 | pgn: this.getMoveHistory(), 90 | move: this._lastMove, 91 | white: (this.white) ? this.white.getPlayer():false, 92 | black: (this.black) ? this.black.getPlayer():false, 93 | turn: this.gameRulesObj.turn(), 94 | gameStarted: this.gameStarted, 95 | }; 96 | } 97 | 98 | move() { 99 | 100 | } 101 | 102 | removePlayerFromAllSeats(player: Player) { 103 | if(this.white && this.white.playerId === player.playerId) { 104 | this.white = null; 105 | } 106 | 107 | if(this.black && this.black.playerId === player.playerId) { 108 | this.black = null; 109 | } 110 | } 111 | 112 | removePlayer(color: string) { 113 | switch(color.charAt(0)) { 114 | case 'w': 115 | this.white = null; 116 | break; 117 | case 'b': 118 | this.black = null; 119 | break; 120 | } 121 | return true; 122 | } 123 | 124 | gameReady(): boolean { 125 | return ( 126 | this.white !== null && 127 | this.black !== null); 128 | } 129 | 130 | outColor(): string { return null; } 131 | 132 | newEngineInstance(roomName: string, io: any) { 133 | this.engineInstance = new SChessEngine(roomName, this.time.increment, this.connection); 134 | } 135 | 136 | startGame() { 137 | this.gameStarted = true; 138 | this.white.alive = true; 139 | this.black.alive = true; 140 | this.resetClocks(); 141 | this.gameRulesObj = new SChessGame(); 142 | this.moveHistory = []; 143 | } 144 | 145 | setPlayerOutByColor(color: string) { 146 | let playerOut = null; 147 | switch(color.charAt(0)) { 148 | case 'w': 149 | this.white.alive = false; 150 | playerOut = this.white; 151 | break; 152 | case 'b': 153 | this.black.alive = false; 154 | playerOut = this.black; 155 | break; 156 | } 157 | } 158 | } -------------------------------------------------------------------------------- /src/containers/presets/quick_match.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | 4 | import {startPairing, stopPairing} from '../../actions/create_game'; 5 | 6 | import Loading from 'react-loading-animation'; 7 | import Select from 'react-select'; 8 | import {Row, Col, Button} from 'react-bootstrap'; 9 | 10 | 11 | const gameTypeOptions = [ 12 | { value: 'standard', label: 'Standard'}, 13 | // { value: 'four-player', label: 'Four Player'}, 14 | { value: 'schess', label: 'S-Chess'}, 15 | { value: 'crazyhouse', label: 'Crazyhouse'}, 16 | { value: 'crazyhouse960', label: 'Crazyhouse 960'}, 17 | // { value: 'four-player-team', label: 'Four Player Teams'} 18 | ]; 19 | 20 | class QuickMatch extends Component { 21 | 22 | constructor(props) { 23 | super(props); 24 | this.state = { 25 | gameType: 'standard', 26 | } 27 | } 28 | 29 | startPairing(timeControl, increment) { 30 | this.props.startPairing(this.state.gameType, timeControl, increment); 31 | } 32 | 33 | stopPairing() { 34 | this.props.stopPairing(); 35 | } 36 | 37 | onSelectGameType(val) { 38 | this.setState({ 39 | gameType: val.value, 40 | }); 41 | } 42 | 43 | render() { 44 | return ( 45 |
    46 |

    47 | 48 | PAIR ME 49 | 50 |

    51 | 52 | 53 | 54 | 23 | 28 |
    29 | ); 30 | } else { 31 | return ( 32 |
    33 | 39 | 44 |
    45 | ); 46 | } 47 | } 48 | } 49 | 50 | class RookToggle extends Component { 51 | constructor() { 52 | super(); 53 | this.rook_img_src = "./img/chesspieces/wikipedia/wR.png"; 54 | } 55 | 56 | render() { 57 | if (this.props.location === "bottom" && this.props.interactive) { 58 | return ( 59 |
    60 | 65 | 72 |
    73 | ); 74 | } else { 75 | return null; 76 | } 77 | } 78 | } 79 | 80 | class SChessHand extends Component { 81 | constructor() { 82 | super(); 83 | } 84 | 85 | componentDidMount() { 86 | $(".schess-piece-wrapper input[type=checkbox]").click(function() { 87 | let checkedState = $(this).prop("checked"); 88 | $(".schess-piece-wrapper input[type=checkbox]") 89 | .prop("checked", false); 90 | $(this).prop("checked", checkedState); 91 | }) 92 | } 93 | 94 | render() { 95 | let interactive = false; 96 | let top_hand = null, bottom_hand = null; 97 | let hand = new SChess(this.props.fen).get_hand(); 98 | if (hand) { 99 | top_hand = hand.b; 100 | bottom_hand = hand.w; 101 | if (this.props.profile._id === this.props.game.black.playerId) { 102 | top_hand = hand.w; 103 | bottom_hand = hand.b; 104 | } 105 | } 106 | if (this.props.location === "top") { 107 | hand = top_hand; 108 | } else { 109 | if (this.props.profile._id === this.props.game.white.playerId || 110 | this.props.profile._id === this.props.game.black.playerId) { 111 | if (this.props.game.gameStarted) { 112 | interactive = true; 113 | } 114 | } 115 | hand = bottom_hand; 116 | } 117 | let elephantEnabled = false, hawkEnabled = false; 118 | if (hand) { 119 | elephantEnabled = typeof (hand.find(p => p.type === 'e')) !== "undefined"; 120 | hawkEnabled = typeof (hand.find(p => p.type === 'h')) !== "undefined"; 121 | } 122 | return ( 123 |
    124 |
    125 | 130 | 135 |
    136 | 140 |
    141 | ); 142 | } 143 | } 144 | 145 | function mapStateToProps(state) { 146 | let profile = state.auth.profile; 147 | let name = state.activeThread; 148 | let room = state.openThreads[name]; 149 | let game = room.game; 150 | let fen = game.fen; 151 | return { 152 | profile, 153 | game, 154 | fen 155 | } 156 | } 157 | 158 | export default connect(mapStateToProps) (SChessHand) -------------------------------------------------------------------------------- /src/components/create_game/new_game_modal_room_options.jsx: -------------------------------------------------------------------------------- 1 | import React, {Component} from 'react'; 2 | import {connect} from 'react-redux'; 3 | import Select from 'react-select'; 4 | import {Button, Row, Col, Glyphicon, OverlayTrigger, Tooltip} from 'react-bootstrap'; 5 | import Slider, { Range } from 'rc-slider'; 6 | import 'rc-slider/assets/index.css'; 7 | import { 8 | enableVoiceChat, 9 | changeMaxPlayers, 10 | togglePrivate, 11 | } from '../../actions/create_game'; 12 | import {showError} from '../../actions'; 13 | import ToggleButton from 'react-toggle-button'; 14 | 15 | import { 16 | selectedGameType, 17 | selectedNewTime, 18 | selectedNewTimeIncrement, 19 | selectedRoomMode, 20 | } from '../../actions/create_game.js'; 21 | import {userSearch, selectedChallengeId} from '../../actions/user'; 22 | 23 | const roomModeOptions = [ 24 | { value: 'open-table', label: 'Open Table' }, 25 | { value: 'match', label: 'Match' }, 26 | ]; 27 | 28 | class NewGameModalGameOptions extends Component { 29 | constructor(props) { 30 | super(props); 31 | this.onSelectRoomMode = this.onSelectRoomMode.bind(this); 32 | this.renderChallengePlayerInput = this.renderChallengePlayerInput.bind(this); 33 | } 34 | 35 | componentDidUpdate(prevProps) { 36 | if (!this.props.userResults) 37 | return; 38 | if (this.deferred) { 39 | this.deferred.resolve(this.props.userResults); 40 | } 41 | } 42 | 43 | setupTypeahead() { 44 | $(".challenge-player-input").typeahead({ 45 | template: "{{username}}", 46 | templateValue: "{{username}}", 47 | order: "asc", 48 | input: ".challenge-player-input", 49 | minLength: 2, 50 | hint: true, 51 | highlight: true, 52 | limit: 10, 53 | dynamic: true, 54 | display: ["username"], 55 | emptyTemplate: 'No users found: "{{query}}"', 56 | callback: { 57 | onClickAfter: (node, a, item, e) => { 58 | if(item && item._id) { 59 | this.props.selectedChallengeId(item._id); 60 | } 61 | } 62 | }, 63 | source: { 64 | data: () => { 65 | let v = $(".challenge-player-input").val(); 66 | if (!v || v === "" || v.length < 2) { 67 | return []; 68 | } 69 | this.props.userSearch(v); 70 | let deferred = $.Deferred(); 71 | this.deferred = deferred; 72 | return deferred; 73 | } 74 | } 75 | }); 76 | } 77 | 78 | componentDidMount() { 79 | this.setupTypeahead(); 80 | } 81 | 82 | onSelectRoomMode(val) { 83 | this.props.selectedRoomMode(val.value); 84 | } 85 | 86 | renderRoomMode() { 87 | return ( 88 |
    89 | 90 | 128 | 129 |
    130 | 131 | ); 132 | } 133 | 134 | render() { 135 | return ( 136 |
    137 | 138 | 139 | {this.renderRoomMode()} 140 | 141 | 142 | {this.renderChallengePlayerInput()} 143 | 144 | 145 |
    146 | ); 147 | } 148 | } 149 | 150 | function mapStateToProps(state) { 151 | return { 152 | newGame: state.newGameOptions, 153 | profile: state.auth.profile, 154 | room: state.newGameOptions.room, 155 | game: state.newGameOptions, 156 | userResults: state.userSearch.userResults 157 | } 158 | } 159 | 160 | export default connect(mapStateToProps, 161 | {selectedGameType, 162 | selectedNewTime, 163 | selectedNewTimeIncrement, 164 | enableVoiceChat, 165 | showError, 166 | changeMaxPlayers, 167 | togglePrivate, 168 | selectedRoomMode, 169 | userSearch, 170 | selectedChallengeId 171 | }) (NewGameModalGameOptions); 172 | --------------------------------------------------------------------------------