├── .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 |
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 |
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 |
12 |
13 |
14 |
15 |
18 |
{value.name} Settings
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 | );
31 | }
32 |
33 | render() {
34 | const {value} = this.props;
35 | if(value.host) {
36 | return (
37 |
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 |
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 |
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 |
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 |
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 |
53 |
54 |
55 | {rooms.length} Game Rooms
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 | | Title |
66 | Game Type |
67 | Time Control |
68 | # Users |
69 |
70 |
71 |
72 | {rooms.map(this.renderRoomItems)}
73 |
74 |
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 |
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 |
;
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 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 | {this.props.searching && (
104 |
105 |
Looking for a {this.props.timeControl}+{this.props.increment} {this.props.gameType} game
106 |
107 |
110 |
111 | )}
112 |
113 |
114 | )
115 | }
116 | }
117 |
118 | function mapStateToProps(state) {
119 | return {
120 | searching: state.matchmaking.searching,
121 | timeControl: state.matchmaking.timeControl,
122 | increment: state.matchmaking.increment,
123 | gameType: state.matchmaking.gameType
124 | };
125 | }
126 |
127 | export default connect(mapStateToProps, {startPairing, stopPairing}) (QuickMatch);
--------------------------------------------------------------------------------
/server/game_models/rooms/Message.ts:
--------------------------------------------------------------------------------
1 | import Player from '../players/Player';
2 |
3 | export class Message {
4 | protected picture: string;
5 | protected username: string;
6 | protected playerId: string;
7 | protected eventType: string = 'chat-message';
8 | protected timeSent = new Date();
9 | protected anonymous: boolean = false;
10 |
11 | constructor(player: Player,
12 | protected messageBody: string,
13 | protected roomName: string) {
14 | if (player) {
15 | this.picture = player.picture;
16 | this.username = player.username;
17 | this.playerId = player.playerId;
18 | this.anonymous = player.anonymous;
19 | }
20 | }
21 |
22 | getMessage() {
23 | return {
24 | user: this.username,
25 | msg: this.messageBody,
26 | picture: this.picture,
27 | event_type: this.eventType,
28 | time: this.timeSent,
29 | playerId: this.playerId,
30 | anonymous: this.anonymous
31 | };
32 | }
33 | }
34 |
35 | export class JoinMessage extends Message {
36 | constructor(player: Player,
37 | messageBody: string,
38 | roomName: string) {
39 | super(player, messageBody, roomName);
40 | this.messageBody = `${this.username} has joined the room.`;
41 | this.eventType = 'user-joined';
42 | }
43 | }
44 |
45 | export class LeaveMessage extends Message {
46 | constructor(player: Player,
47 | messageBody: string,
48 | roomName: string) {
49 | super(player, messageBody, roomName);
50 | this.messageBody = `${this.username} has left the room.`;
51 | this.eventType = 'user-left';
52 | }
53 | }
54 |
55 | export class ResignMessage extends Message {
56 | constructor(player: Player,
57 | messageBody: string,
58 | roomName: string) {
59 | super(player, messageBody, roomName);
60 | this.messageBody = `${this.username} has resigned.`;
61 | this.eventType = 'player-resigned';
62 | }
63 | }
64 |
65 | export class CheckmateMessage extends Message {
66 | constructor(player: Player,
67 | messageBody: string,
68 | roomName: string) {
69 | super(player, messageBody, roomName);
70 | this.messageBody = `${this.username} was checkmated.`;
71 | this.eventType = 'player-checkmated';
72 | }
73 | }
74 |
75 | export class TimeForfeitMessage extends Message {
76 | constructor(player: Player,
77 | messageBody: string,
78 | roomName: string) {
79 | super(player, messageBody, roomName);
80 | this.messageBody = `${this.username} forfeits on time.`;
81 | this.eventType = 'player-time-forfeit';
82 | }
83 | }
84 |
85 | export class WinnerMessage extends Message {
86 | constructor(player: Player,
87 | messageBody: string,
88 | roomName: string) {
89 | super(player, messageBody, roomName);
90 | this.messageBody = `${this.username} is the winner!`;
91 | this.eventType = 'player-winner';
92 | }
93 | }
94 |
95 | export class DrawMessage extends Message {
96 | constructor(player: Player,
97 | messageBody: string,
98 | roomName: string) {
99 | super(player, messageBody, roomName);
100 | this.messageBody = `The game ended in a draw.`;
101 | this.eventType = 'game-draw';
102 | }
103 | }
104 |
105 | export class DrawOfferMessage extends Message {
106 | constructor(player: Player,
107 | messageBody: string,
108 | roomName: string) {
109 | super(player, messageBody, roomName);
110 | this.messageBody = `${this.username} offered a draw.`;
111 | this.eventType = 'draw-offer';
112 | }
113 | }
114 |
115 | export class EliminationMessage extends Message {
116 | constructor(player: Player,
117 | messageBody: string,
118 | roomName: string) {
119 | super(player, messageBody, roomName);
120 | this.messageBody = `${this.username} has been eliminated.`;
121 | this.eventType = 'player-eliminated';
122 | }
123 | }
124 |
125 | export class AbortMessage extends Message {
126 | constructor(player: Player,
127 | messageBody: string,
128 | roomName: string) {
129 | super(player, messageBody, roomName);
130 | this.messageBody = `${this.username} aborted the game.`;
131 | this.eventType = 'game-aborted';
132 | }
133 | }
134 |
135 | export class GameStartedMessage extends Message {
136 | constructor(players: Player[],
137 | messageBody: string,
138 | roomName: string) {
139 | super(null, messageBody, roomName);
140 | this.messageBody =
141 | `The game has begun: ${players.map(p => p.username).join(' vs. ')}`;
142 | this.eventType = 'game-started';
143 | }
144 | }
--------------------------------------------------------------------------------
/src/containers/auth_card.jsx:
--------------------------------------------------------------------------------
1 | import React, { Component, PropTypes } from 'react';
2 | import { connect, bindActionCreators } from 'react-redux';
3 | import { Link } from 'react-router';
4 | import FacebookLogin from 'react-facebook-login';
5 | import GoogleLogin from 'react-google-login';
6 | import YouTube from 'react-youtube';
7 | import { loginUser, signUpUser, fbLoginUser, googleLoginUser, anonLogin } from '../actions/index';
8 | import App from './app';
9 | import SignUpForm from '../components/auth/signup_form';
10 | import LoginForm from '../components/auth/login_form';
11 | import config from '../../config/config';
12 |
13 | import {Row, Col, Clearfix, Button, Panel, PanelGroup, Accordion, Grid} from 'react-bootstrap';
14 |
15 | class AuthCard extends Component {
16 |
17 | onSignUpSubmit(values) {
18 | this.props.signUpUser(values);
19 | }
20 |
21 | onLoginSubmit(values) {
22 | this.props.loginUser(values);
23 | }
24 |
25 | onAnonLogin(values) {
26 | this.props.anonLogin();
27 | }
28 |
29 | fbCallback(response) {
30 | //send user credentials to server
31 | this.props.fbLoginUser(response.accessToken);
32 | }
33 |
34 | googleCallback(response) {
35 | this.props.googleLoginUser(response.accessToken);
36 | }
37 |
38 | componentWillMount() {
39 | this.refs.signupCol
40 | }
41 |
42 | render() {
43 | const { errorMessage } = this.props;
44 |
45 | const opts = {
46 | height: '500px',
47 | width: '100%',
48 | playerVars: { // https://developers.google.com/youtube/player_parameters
49 | autoplay: 0
50 | },
51 |
52 | };
53 | return (
54 |
55 |
56 | Sign Up
57 |
58 |
59 |
60 |
61 |
62 |
63 |
72 |
73 |
74 |
78 | Connect with Google
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
105 |
106 |
107 |
108 | Terms of Service and Privacy Policy
109 |
110 |
111 |
112 |
113 |
114 | Login
115 |
116 |
117 |
118 |
119 |
120 | );
121 | }
122 | }
123 |
124 | App.contextTypes = {
125 | store: PropTypes.object
126 | };
127 |
128 | function mapStateToProps(state) {
129 | return {
130 | errorMessage: state.auth.error,
131 | };
132 | }
133 |
134 | export default connect (
135 | mapStateToProps,
136 | {loginUser, signUpUser, fbLoginUser, googleLoginUser, anonLogin}
137 | ) (AuthCard);
138 |
--------------------------------------------------------------------------------
/server/game_models/games/CrazyHouse.ts:
--------------------------------------------------------------------------------
1 | import Game from './Game';
2 | const {Crazyhouse} = require('crazyhouse.js');
3 | const {User} = require('../../models/user');
4 | const {CrazyhouseGame} = require('../../models/crazyhouse');
5 | const {Crazyhouse960Game} = require('../../models/crazyhouse960');
6 | const Elo = require('elo-js');
7 | import Player from '../players/Player';
8 | import CrazyEngine from '../../engine/CrazyEngine';
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 CrazyHouse extends Game {
46 | gameType: string = 'crazyhouse';
47 | gameRulesObj: any = new Crazyhouse();
48 | startPos: string;
49 | numPlayers: number = 2;
50 | io: any;
51 | times: any = {
52 | w: 0,
53 | b: 0
54 | };
55 | time: any;
56 | set_960: boolean = false;
57 | connection: Connection;
58 | ratings_type: string = "crazyhouse_ratings";
59 | gameClassDB: any = CrazyhouseGame;
60 |
61 | constructor(io: Object, roomName:string, time: any, set_960: boolean, connection: Connection) {
62 | super();
63 | this.io = io;
64 | this.roomName = roomName;
65 | this.time = time;
66 | let initialTime = time.value * 60 * 1000;
67 | this.times = {
68 | w: initialTime,
69 | b: initialTime
70 | };
71 | this.connection = connection;
72 | this.set_960 = set_960;
73 | if(set_960) {
74 | this.gameType = 'crazyhouse960';
75 | this.ratings_type = "crazyhouse960_ratings";
76 | this.gameRulesObj = new Crazyhouse({960: true});
77 | this.gameClassDB = Crazyhouse960Game;
78 | } else {
79 | this.gameRulesObj = new Crazyhouse();
80 | }
81 | }
82 |
83 |
84 | setPlayerResignByPlayerObj(player: Player) {
85 | if(this.white && this.white.playerId === player.playerId) {
86 | this.white.alive = false;
87 | }
88 |
89 | if(this.black && this.black.playerId === player.playerId) {
90 | this.black.alive = false;
91 | }
92 | }
93 |
94 | getGame() {
95 | return {
96 | numPlayers: this.numPlayers,
97 | gameType: this.gameType,
98 | fen: this.gameRulesObj.fen(),
99 | pgn: this.getMoveHistory(),
100 | move: this._lastMove,
101 | white: (this.white) ? this.white.getPlayer():false,
102 | black: (this.black) ? this.black.getPlayer():false,
103 | turn: this.gameRulesObj.turn(),
104 | gameStarted: this.gameStarted,
105 | startPos: this.startPos
106 | };
107 | }
108 |
109 | move() {
110 |
111 | }
112 |
113 | removePlayerFromAllSeats(player: Player) {
114 | if(this.white && this.white.playerId === player.playerId) {
115 | this.white = null;
116 | }
117 |
118 | if(this.black && this.black.playerId === player.playerId) {
119 | this.black = null;
120 | }
121 | }
122 |
123 | removePlayer(color: string) {
124 | switch(color.charAt(0)) {
125 | case 'w':
126 | this.white = null;
127 | break;
128 | case 'b':
129 | this.black = null;
130 | break;
131 | }
132 | return true;
133 | }
134 |
135 | gameReady(): boolean {
136 | return (
137 | this.white !== null &&
138 | this.black !== null);
139 | }
140 |
141 | outColor(): string { return null; }
142 |
143 | newEngineInstance(roomName: string, io: any) {
144 | this.engineInstance = new CrazyEngine(roomName, this.time.increment, this.connection, this.set_960);
145 | }
146 |
147 | startGame() {
148 | this.gameStarted = true;
149 | this.white.alive = true;
150 | this.black.alive = true;
151 | this.resetClocks();
152 | this.gameRulesObj = new Crazyhouse({960: this.set_960});
153 | if (this.set_960) {
154 | this.startPos = this.gameRulesObj.fen();
155 | }
156 | this.moveHistory = [];
157 | }
158 |
159 |
160 | setPlayerOutByColor(color: string) {
161 | let playerOut = null;
162 | switch(color.charAt(0)) {
163 | case 'w':
164 | this.white.alive = false;
165 | playerOut = this.white;
166 | break;
167 | case 'b':
168 | this.black.alive = false;
169 | playerOut = this.black;
170 | break;
171 | }
172 | }
173 | }
--------------------------------------------------------------------------------
/src/containers/board/schess_hand.jsx:
--------------------------------------------------------------------------------
1 | import React, {Component} from 'react';
2 | import {connect} from 'react-redux';
3 |
4 | import SChess from 'schess.js';
5 |
6 | class Piece extends Component {
7 | constructor() {
8 | super();
9 | }
10 |
11 | render() {
12 | let id = "schess-" + this.props.type + '-' + this.props.location;
13 | let gone = this.props.enabled ? "" : " gone";
14 | if (this.props.interactive) {
15 | return (
16 |
17 |
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 |
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 |
97 |
98 | );
99 | }
100 |
101 | renderChallengePlayerInput() {
102 | let display;
103 | if (this.props.room.roomMode === "match") {
104 | display = "block";
105 | } else {
106 | display = "none";
107 | }
108 | const tooltip =
109 |
110 | Leave blank to allow the first player seated to play.
111 | ;
112 | return (
113 |
114 |
115 |
116 |
117 |
121 |
122 |
123 |
124 |
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 |
--------------------------------------------------------------------------------