Compare commits
No commits in common. "master" and "auth" have entirely different histories.
223 changed files with 5103 additions and 21346 deletions
|
@ -1,55 +0,0 @@
|
||||||
# JavaScript Node CircleCI 2.0 configuration file
|
|
||||||
#
|
|
||||||
# Check https://circleci.com/docs/2.0/language-javascript/ for more details
|
|
||||||
#
|
|
||||||
version: 2
|
|
||||||
|
|
||||||
commands:
|
|
||||||
create_concatenated_package_lock:
|
|
||||||
description: "Concatenate all package-lock.json files recognized by lerna.js into single file. File is used as checksum source for part of caching key."
|
|
||||||
parameters:
|
|
||||||
filename:
|
|
||||||
type: string
|
|
||||||
steps:
|
|
||||||
- run:
|
|
||||||
name: Combine package-lock.json files to single file
|
|
||||||
command: npx lerna list -p -a | awk -F packages '{printf "\"packages%s/package-lock.json\" ", $2}' | xargs cat > << parameters.filename >>
|
|
||||||
jobs:
|
|
||||||
build:
|
|
||||||
docker:
|
|
||||||
# specify the version you desire here
|
|
||||||
- image: circleci/node:12.6
|
|
||||||
|
|
||||||
working_directory: ~/node-go
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- checkout
|
|
||||||
# - run: sudo apt-get install postgresql-client-11.4
|
|
||||||
# - run: pg_ctl -D /var/lib/postgresql/data -l logfile start
|
|
||||||
# - run: psql CREATE DATABASE circle_test
|
|
||||||
|
|
||||||
# Download and cache dependencies
|
|
||||||
- restore_cache:
|
|
||||||
keys:
|
|
||||||
- v1-dependencies-{{ checksum "package-lock.json" }}
|
|
||||||
# fallback to using the latest cache if no exact match is found
|
|
||||||
|
|
||||||
|
|
||||||
- run: npm install
|
|
||||||
- run: npm run bootstrap
|
|
||||||
|
|
||||||
- save_cache:
|
|
||||||
paths:
|
|
||||||
- node_modules
|
|
||||||
key:
|
|
||||||
v1-dependencies-{{ checksum "package-lock.json" }}
|
|
||||||
- run: npm test
|
|
||||||
- run: npm install react
|
|
||||||
- run: npm install react-dom
|
|
||||||
|
|
||||||
# ! temporary fix for deprecated package: minimist
|
|
||||||
- run: npm audit --audit-level=moderate
|
|
||||||
|
|
||||||
# DEPLOY
|
|
||||||
- run: git subtree push --prefix packages/server https://heroku:$HEROKU_API_KEY@git.heroku.com/$HK_API.git master
|
|
||||||
- run: git subtree push --prefix packages/play-node-go https://heroku:$HEROKU_API_KEY@git.heroku.com/$HK_PLAY.git master
|
|
21
LICENSE
21
LICENSE
|
@ -1,21 +0,0 @@
|
||||||
MIT License
|
|
||||||
|
|
||||||
Copyright (c) 2021 Sorrel
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
171
README.md
171
README.md
|
@ -1,170 +1 @@
|
||||||
# Node Go
|
[Visit site](https://play-node-go.herokuapp.com/)
|
||||||
A browser application to play Go in real time.
|
|
||||||
|
|
||||||
[The project in it's current state](https://play-node-go.herokuapp.com/)
|
|
||||||
[Client only prototype](https://sorrelbri.github.io/browser-go-proto/)
|
|
||||||
|
|
||||||
![Screenshot of an in-progress game of Go.](public/game-in-progress.png)
|
|
||||||
|
|
||||||
[About Go](#the-game-of-go)
|
|
||||||
[Technical Challenges](#technical-challenges)
|
|
||||||
[Setup For Development](#setup)
|
|
||||||
[Known Bugs](#known-bugs)
|
|
||||||
[Roadmap](#roadmap)
|
|
||||||
[Features](#features)
|
|
||||||
[Tech](#built-with)
|
|
||||||
|
|
||||||
---
|
|
||||||
## The Game of Go
|
|
||||||
Go is a 2 player abstract strategy game of perfect information.
|
|
||||||
|
|
||||||
Players take turns placing playing pieces called stones on the intersections of a gridded board. This board is usually a square 19 points across. Stones remain on the points at which they are placed unless they are captured by the opposing player. Capture occurs when a stone or group of stones no longer has any adjascent empty points.
|
|
||||||
|
|
||||||
Play ends when both players agree that they have exhausted all advantageous moves. Scoring is determined by counting and comparing the area controlled by either player.
|
|
||||||
|
|
||||||
For a more detailed explanation of the rules, please see [my previous illustrated explanation of the game of go](https://github.com/sorrelbri/browser-go-proto#the-game-of-go) or the [American Go Association's Concise Rules of Go.](https://www.usgo.org/aga-concise-rules-go)
|
|
||||||
|
|
||||||
---
|
|
||||||
## Technical Challenges
|
|
||||||
### Modeling Game State
|
|
||||||
A go board typically consists of 361 points which can exist in a number of states. Points can influence the state of points that are orthogonal neighbors. This relationship can be thought of as an undirected graph, with each point being a vertex typically of degree 4. Special cases include 'edge' points, whose degree is 3 or, in the case of corner points, 2.
|
|
||||||
|
|
||||||
Many of the methods that manage the state of the game and of the board, make use of this graph representation. Groups are contiguous points with the same color stone which are important in determining the life or death of stones on the board. When a player makes a move, (provided that move is legal,) the point at which the move is made will utilize a breadth-first graph traversal calling a `joinGroup` method on each point with the same color stone.
|
|
||||||
|
|
||||||
Adjacent points without stones are very important to the state of a point as well. These are known as liberties, and so long as a group of stones has at least one point with at least one liberty, that group remains alive and on the board. Therefore, the `joinGroup` method also utilizes a depth first traversal to mark all of the liberties of a group. Both the stones and the liberties of the group are memoized on the Game object.
|
|
||||||
|
|
||||||
![Image of Game logic](public/game-logic.png)
|
|
||||||
|
|
||||||
### Game Records
|
|
||||||
For the moment, game records are modeled as a list where each move has the type `{player: <string: player color>, pos: {x: <integer>, y: <integer>}}`.
|
|
||||||
|
|
||||||
The database `move` table contains additional information `number`, `game_record`, and `prior_move` in addition to a foreign key for a `game` row. `number` represents the move number in a game record (for now this corresponds to list position), `game_record` is a boolean representing whether the move was 'official' or is an alternate move used in study, and `prior_move` is a reference to another `move` row allowing for the construction of a tree model for the game (see [Expanding this representation](#expanding-this-representation), below.)
|
|
||||||
|
|
||||||
There is a backend service that processes this list of moves into a current board state ([Modeling Game State](#modeling-game-state).) On the frontend, users have the option of expanding a menu to view the move order in the format below.
|
|
||||||
|
|
||||||
![Game Record: Black and white 19x19 grid with moves represented as circles filled in with stone color and marked with move number. Below the grid is an overflow area for moves made at previously played points in the format /[new move number and color at old move number and color/]](public/game-record.png)
|
|
||||||
|
|
||||||
This is a customary representation of game records printed in Go literature. A frontend service processes the list of moves and plots each move onto a `<canvas>` element drawn to resemble the grid lines on a board, with moves that are placed at prior points plotted on an additional `<canvas>` element below.
|
|
||||||
|
|
||||||
#### Expanding this representation
|
|
||||||
This representation will be expanded when support for alternate game paths is added. Alternate game paths will allow users to study completed games by playing out the consequences of moves not in the completed game. This feature will require a tree structure for the game record where moves on the main line are referred with a `main` property on each move node and alternate moves with any number of alternate references.
|
|
||||||
|
|
||||||
### Caching multiple in-progress games
|
|
||||||
![Image of Game module in context](public/game-module.png)
|
|
||||||
|
|
||||||
### Partitioning Game Rooms
|
|
||||||
Finding a game starts with joining a game room.
|
|
||||||
![Image of Home Screen with 'main' game room](public/home-screen.png)
|
|
||||||
Watch an in progress game, join a game, or study a historic game.
|
|
||||||
![Image of Room Screen with multiple games in various states](public/room-screen.png)
|
|
||||||
|
|
||||||
---
|
|
||||||
## Setup
|
|
||||||
### Local Repo
|
|
||||||
```sh
|
|
||||||
$ git clone https://github.com/sorrelbri/node-go.git
|
|
||||||
```
|
|
||||||
|
|
||||||
### Install Deps
|
|
||||||
```sh
|
|
||||||
$ npm run bootstrap
|
|
||||||
```
|
|
||||||
Runs lerna `bootstrap` command installing dependencies for project, development and package dependencies
|
|
||||||
|
|
||||||
### Initialize Database
|
|
||||||
#### Download PostgreSQL
|
|
||||||
To verify PostgreSQL installation:
|
|
||||||
```sh
|
|
||||||
$ psql -V
|
|
||||||
```
|
|
||||||
Node Go API was built with version 11.4.
|
|
||||||
[See documentation for Postgres download.](https://www.postgresql.org/download/)
|
|
||||||
|
|
||||||
#### Create Databases
|
|
||||||
```sh
|
|
||||||
$ psql
|
|
||||||
# psql(11.4)
|
|
||||||
```
|
|
||||||
```sql
|
|
||||||
CREATE DATABASE node-go;
|
|
||||||
CREATE DATABASE node-go-test; # testing database
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configure Environment
|
|
||||||
```sh
|
|
||||||
$ touch packages/server/.env
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
# .env
|
|
||||||
NODE_ENV=development
|
|
||||||
PORT=# set development port for API
|
|
||||||
REACT_ADDRESS=http://localhost:3000 # default
|
|
||||||
PG_CONNECTION_STRING=postgresql://localhost:5432/node-go
|
|
||||||
PG_CONNECTION_STRING_TEST=postgresql://localhost:5432/node-go-test
|
|
||||||
JWT_SECRET=# generate a secret key for JWT signing algorithm
|
|
||||||
TEST_SECRET=# same as above, for test environment
|
|
||||||
SALT_ROUNDS=# set number of salt rounds for bcrypt hashing function
|
|
||||||
DOMAIN=localhost
|
|
||||||
USER_ONE_PASSWORD=# credentials for testing with
|
|
||||||
USER_ONE_EMAIL=# same as above
|
|
||||||
```
|
|
||||||
|
|
||||||
### Smoke test
|
|
||||||
```sh
|
|
||||||
$ lerna run test
|
|
||||||
```
|
|
||||||
|
|
||||||
### Run Database Migrations
|
|
||||||
```sh
|
|
||||||
$ cd packages/server; npm run migrate; npm run seed
|
|
||||||
```
|
|
||||||
|
|
||||||
### Running in development
|
|
||||||
```sh
|
|
||||||
$ cd packages/server
|
|
||||||
$ npm start # or if you have nodemon
|
|
||||||
$ nodemon
|
|
||||||
```
|
|
||||||
```sh
|
|
||||||
$ cd packages/play-node-go
|
|
||||||
$ npm start
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
## Known Bugs
|
|
||||||
- game end logic not implemented on front end yet
|
|
||||||
- no authorization for game moves
|
|
||||||
- websocket connections may remain open, pooling prevents runaway leaks, but tests may hang
|
|
||||||
|
|
||||||
---
|
|
||||||
## Roadmap
|
|
||||||
### 6/20
|
|
||||||
1. Frontend implementation of game end logic
|
|
||||||
2. Auth for games
|
|
||||||
3. Game request creation
|
|
||||||
### 7/20
|
|
||||||
1. Generate game records
|
|
||||||
2. Implement chat
|
|
||||||
3. Implement study mode
|
|
||||||
|
|
||||||
---
|
|
||||||
## Features
|
|
||||||
- [x] Realtime play
|
|
||||||
- [x] Account authentication
|
|
||||||
- [ ] Chat
|
|
||||||
- [ ] Study mode
|
|
||||||
- [ ] Multiple game settings
|
|
||||||
- [ ] Customizable board size
|
|
||||||
- [ ] Download games in .sgf format
|
|
||||||
|
|
||||||
---
|
|
||||||
## Built with
|
|
||||||
- [Express](https://expressjs.com)
|
|
||||||
- [React](https://reactjs.org)
|
|
||||||
- [PostgreSQL](https://postgresql.org)
|
|
||||||
- [Socket.io](https://socket.io)
|
|
||||||
- [Sass](https://sass-lang.com)
|
|
||||||
### Management & Deployment
|
|
||||||
- Lerna
|
|
||||||
- CircleCI
|
|
|
@ -1,6 +0,0 @@
|
||||||
{
|
|
||||||
"packages": [
|
|
||||||
"packages/*"
|
|
||||||
],
|
|
||||||
"version": "independent"
|
|
||||||
}
|
|
8014
package-lock.json
generated
8014
package-lock.json
generated
File diff suppressed because it is too large
Load diff
18
package.json
18
package.json
|
@ -1,18 +0,0 @@
|
||||||
{
|
|
||||||
"name": "root",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
|
||||||
"test": "lerna run test",
|
|
||||||
"bootstrap": "lerna bootstrap --hoist"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@babel/preset-flow": "^7.8.3",
|
|
||||||
"@testing-library/jest-dom": "^4.2.4",
|
|
||||||
"@testing-library/react": "^9.4.0",
|
|
||||||
"chai": "^4.2.0",
|
|
||||||
"chai-http": "^4.3.0",
|
|
||||||
"lerna": "^3.20.2",
|
|
||||||
"mocha": "^7.0.0",
|
|
||||||
"react-test-renderer": "^16.12.0"
|
|
||||||
}
|
|
||||||
}
|
|
Binary file not shown.
Before Width: | Height: | Size: 318 B |
|
@ -1,21 +0,0 @@
|
||||||
@mixin fullspan {
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin flexAround($direction) {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: $direction;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-around;
|
|
||||||
}
|
|
||||||
|
|
||||||
@mixin gameViewLabel {
|
|
||||||
color: rgb(255,240,230);
|
|
||||||
background-color: rgba(0,0,0,0.7);
|
|
||||||
padding: 0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
@function place-below($grid-column, $grid-row) {
|
|
||||||
@return $grid-row + 1 + '/' + $grid-column + 2 + '/ span 1 / span 1';
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
$dev-colors: (
|
|
||||||
"a": #34dc90,
|
|
||||||
"b": #9034dc,
|
|
||||||
"c": #dc9034,
|
|
||||||
"d": #dc34dc,
|
|
||||||
"e": #34dcdc,
|
|
||||||
"f": #dcdc34
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Responsive Design */
|
|
||||||
$break-points: (
|
|
||||||
"500": "only screen and (min-width: 500px)",
|
|
||||||
"570": "only screen and (min-width: 570px)",
|
|
||||||
"590": "only screen and (min-width: 590px)",
|
|
||||||
"700": "only screen and (min-width: 700px)",
|
|
||||||
"900": "only screen and (min-width: 900px)"
|
|
||||||
);
|
|
||||||
|
|
||||||
$colors: (
|
|
||||||
"error": #aa3333,
|
|
||||||
"nav_bar": #6f3e68,
|
|
||||||
"nav_link": #dae1b7,
|
|
||||||
"sidebar": #3c013f,
|
|
||||||
"sidebar_link": #f2ce3d,
|
|
||||||
"main": #95acae,
|
|
||||||
"home": #444444
|
|
||||||
);
|
|
||||||
|
|
||||||
$backgrounds: (
|
|
||||||
"game_room": radial-gradient(farthest-corner at 55% 40%, rgb(189, 131, 100) 0%, rgb(175, 113, 80) 65%, rgb(150, 90, 65) 90%, rgb(125, 65, 40) 100%),
|
|
||||||
);
|
|
||||||
|
|
||||||
/* Game Room Components */
|
|
||||||
$small-room: (
|
|
||||||
"roof": linear-gradient(rgb(69, 36, 19), rgb(31, 22, 18)),
|
|
||||||
"body": rgb(174, 169, 120),
|
|
||||||
"door": radial-gradient(15vh 20vh at top left, #662413,#992413 15%, #cc3423),
|
|
||||||
"door-handle": radial-gradient(circle at 40% 30%, rgb(247, 235, 183), rgb(213, 222, 41) 30%, rgb(121, 108, 7)),
|
|
||||||
"window-x": linear-gradient(rgba(67, 149, 159, 0.4), rgba(196, 249, 255, 0.4) 50%, rgba(67, 149, 159, 0.4) 50%, rgba(196, 249, 255, 0.4)),
|
|
||||||
"window-y": linear-gradient(to right, rgba(67, 149, 159, 0.5), rgba(196, 249, 255, 0.5) 50%, rgba(67, 149, 159, 0.5) 50%, rgba(196, 249, 255, 0.5)),
|
|
||||||
"frame": rgb(69, 36, 19),
|
|
||||||
"sign": rgb(188, 127, 95)
|
|
||||||
);
|
|
|
@ -1,82 +0,0 @@
|
||||||
import React, { useEffect, useReducer } from 'react';
|
|
||||||
import { Switch, Route, BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import MainWrapper from './pages/Layout/MainWrapper/MainWrapper';
|
|
||||||
import { stateReducer } from './reducers/reducer';
|
|
||||||
import { initState } from './reducers/init/reducer.init';
|
|
||||||
import indexServices from './services/api/indexServices';
|
|
||||||
import './App.scss';
|
|
||||||
|
|
||||||
|
|
||||||
function App() {
|
|
||||||
const [ state, dispatch ] = useReducer(
|
|
||||||
stateReducer,
|
|
||||||
{},
|
|
||||||
initState
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchIndexAPI = async () => {
|
|
||||||
const response = await indexServices.indexService();
|
|
||||||
if (response) {
|
|
||||||
const action = {
|
|
||||||
type: 'INDEX',
|
|
||||||
message: 'SET_USER',
|
|
||||||
body: response
|
|
||||||
}
|
|
||||||
dispatch(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchIndexAPI();
|
|
||||||
}, [ ])
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const socketConnect = () => {
|
|
||||||
if (state.connect.type) return;
|
|
||||||
dispatch({type:'SOCKET', message: 'LAUNCH', body:{nsp:'', dispatch}});
|
|
||||||
}
|
|
||||||
socketConnect();
|
|
||||||
|
|
||||||
return () => dispatch({type: 'SOCKET', message: 'DISCONNECT', body: {}});
|
|
||||||
}, [ state.connect ])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Router>
|
|
||||||
|
|
||||||
<div data-testid="App" className="App">
|
|
||||||
<Switch>
|
|
||||||
|
|
||||||
<Route path="/account">
|
|
||||||
<MainWrapper page="account" state={state} dispatch={dispatch}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/rooms/:id">
|
|
||||||
<MainWrapper page="room" state={state} dispatch={dispatch}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/games/:id">
|
|
||||||
<MainWrapper page="game" state={state} dispatch={dispatch}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/news">
|
|
||||||
<MainWrapper page="news" state={state} dispatch={dispatch}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/home">
|
|
||||||
<MainWrapper page="home" state={state} dispatch={dispatch}/>
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
<Route path="/">
|
|
||||||
{state.user.username
|
|
||||||
? <MainWrapper page="home" state={state} dispatch={dispatch}/>
|
|
||||||
: <MainWrapper page="news" state={state} dispatch={dispatch}/>}
|
|
||||||
</Route>
|
|
||||||
|
|
||||||
</Switch>
|
|
||||||
<p className="App__socket-flag">{state.connect.type ? '✓' : ' ⃠'}</p>
|
|
||||||
</div>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default App;
|
|
|
@ -1,115 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import './Game.scss';
|
|
||||||
|
|
||||||
const GameButton = (props) => {
|
|
||||||
const { game, user } = props;
|
|
||||||
|
|
||||||
const setGameDisplayData = () => {
|
|
||||||
const gameData = {
|
|
||||||
playerBlack: game.playerBlack,
|
|
||||||
playerBlackRank: game.playerBlackRank,
|
|
||||||
gameId: game.id,
|
|
||||||
}
|
|
||||||
if (game.open) {
|
|
||||||
gameData.gameLinkText = 'Request to Join Game';
|
|
||||||
gameData.playerWhite = '';
|
|
||||||
gameData.playerWhiteRank = 'could be you!';
|
|
||||||
}
|
|
||||||
if (!game.open) {
|
|
||||||
gameData.playerWhite = game.playerWhite;
|
|
||||||
gameData.playerWhiteRank = game.playerWhiteRank;
|
|
||||||
gameData.gameLinkText = game.winType ? 'Study Game'
|
|
||||||
: user ? 'Rejoin Game' : 'Watch Game'
|
|
||||||
}
|
|
||||||
return gameData;
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO add to open game link
|
|
||||||
// const requestJoinGame = () => {
|
|
||||||
// console.log(`request to Join Game ${game.id}!`)
|
|
||||||
// const requestAction = {
|
|
||||||
// type: 'GAMES',
|
|
||||||
// message: 'JOIN_REQUEST',
|
|
||||||
// body: {id: game.id}
|
|
||||||
// }
|
|
||||||
// dispatch(requestAction);
|
|
||||||
// }
|
|
||||||
|
|
||||||
const renderGame = () => {
|
|
||||||
const {
|
|
||||||
gameLinkText,
|
|
||||||
playerBlack,
|
|
||||||
playerBlackRank,
|
|
||||||
gameId,
|
|
||||||
playerWhite,
|
|
||||||
playerWhiteRank
|
|
||||||
} = setGameDisplayData();
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="GameButton__seat GameButton__seat--black">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="GameButton__table">
|
|
||||||
<div className="GameButton__table__meta">
|
|
||||||
<div
|
|
||||||
className="GameButton__player-data GameButton__player-data--black"
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="GameButton__player-data__name GameButton__player-data__name--black"
|
|
||||||
>{playerBlack}</span>
|
|
||||||
<span
|
|
||||||
className="GameButton__player-data__rank GameButton__player-data__rank"
|
|
||||||
>{playerBlackRank}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Link
|
|
||||||
className="GameButton__link"
|
|
||||||
to={`/games/${gameId}`}
|
|
||||||
>{gameLinkText}</Link>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`GameButton__player-data GameButton__player-data--white ${
|
|
||||||
playerWhite ? '' : 'GameButton__player-data--invisible'
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className="GameButton__player-data__name GameButton__player-data__name--white"
|
|
||||||
>{playerWhite}</span>
|
|
||||||
<span
|
|
||||||
className="GameButton__player-data__rank GameButton__player-data__rank--white"
|
|
||||||
>{playerWhiteRank}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="GameButton__table__image">
|
|
||||||
<div className="table__player-area table__player-area--black">
|
|
||||||
<div className="table__player-bowl table__player-bowl--black"></div>
|
|
||||||
</div>
|
|
||||||
<div className="table__game-board">
|
|
||||||
<div className="table__game-board--grid"></div>
|
|
||||||
</div>
|
|
||||||
<div className="table__player-area table__player-area--white">
|
|
||||||
<div className="table__player-bowl table__player-bowl--white"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="GameButton__seat GameButton__seat--white">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="GameButton" data-testid="GameButton">
|
|
||||||
{renderGame()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GameButton;
|
|
|
@ -1,139 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
|
|
||||||
div.GameButton {
|
|
||||||
align-items: stretch;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
height: 20vh;
|
|
||||||
justify-content: space-around;
|
|
||||||
margin: 1vw;
|
|
||||||
width: 20vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__seat {
|
|
||||||
background: linear-gradient(190deg, rgb(232, 78, 78), rgb(172, 18, 18) 40%, rgb(100, 0, 0));
|
|
||||||
height: 10%;
|
|
||||||
margin: 0 auto;
|
|
||||||
width: 50%;
|
|
||||||
|
|
||||||
&.GameButton__seat--black {
|
|
||||||
border: rgba(83, 53, 35, 0.81);
|
|
||||||
border-radius: 5vw 5vw 0 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.GameButton__seat--white {
|
|
||||||
border: transparent;
|
|
||||||
border-radius: 0 0 5vw 5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__table {
|
|
||||||
box-shadow: -2vmin 4vmin 2vmin rgba(83, 53, 35, 0.81);
|
|
||||||
height: 80%;
|
|
||||||
margin: 0;
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__table__meta {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__table__meta * {
|
|
||||||
z-index: 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__table__image {
|
|
||||||
align-items: center;
|
|
||||||
background: radial-gradient(
|
|
||||||
farthest-corner at 55% 40%,
|
|
||||||
rgb(150, 200, 220) 0%, rgb(97, 166, 194) 65%, rgb(70,100,120) 100%
|
|
||||||
);
|
|
||||||
border-top: 0.1vw solid rgb(150, 200, 220);
|
|
||||||
border-right: 0.05vw solid rgb(97, 166, 194);
|
|
||||||
border-radius: 0.3vw;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: space-between;
|
|
||||||
position: relative;
|
|
||||||
top: -100%;
|
|
||||||
width: 100%;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
div.table__game-board {
|
|
||||||
align-items: center;
|
|
||||||
background: radial-gradient(
|
|
||||||
farthest-corner at 55% 40%,
|
|
||||||
rgb(244, 230, 120) 0%, rgb(234, 178, 78) 65%, rgb(200, 160, 90) 90%, rgb(200, 140, 90) 100%
|
|
||||||
);
|
|
||||||
box-shadow: -0.5vh 1vh 2vh rgba(30,30,30, 0.7);
|
|
||||||
height: 60%;
|
|
||||||
justify-content: center;
|
|
||||||
width: 60%;
|
|
||||||
display: flex;
|
|
||||||
|
|
||||||
div.table__game-board--grid {
|
|
||||||
background:
|
|
||||||
repeating-linear-gradient(0deg, #000 0%, transparent 0.75%, transparent 4.25%, #000 5%),
|
|
||||||
repeating-linear-gradient(90deg, #000 0%, transparent 0.75%, transparent 4.25%, #000 5%);
|
|
||||||
height: 94%;
|
|
||||||
width: 94%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.table__player-area {
|
|
||||||
height: 20%;
|
|
||||||
width: 60%;
|
|
||||||
|
|
||||||
div.table__player-bowl {
|
|
||||||
background: radial-gradient(
|
|
||||||
farthest-corner at 48% 54%,
|
|
||||||
rgb(30, 5, 0) 0%, rgb(30, 5, 0) 2%, rgb(30, 5, 0) 32%, rgb(0,0,0)35%, rgb(116,48,17) 48%,
|
|
||||||
rgb(140, 60, 40) 52%, rgb(100, 40, 5) 55%, rgb(116, 48, 17) 58%, rgb(140,60,40) 65%,
|
|
||||||
rgb(100, 40, 5) 80%, rgb(80, 20, 0) 90%
|
|
||||||
);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: -0.5vh 1vh 2vh rgba(30,30,30, 0.7);
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
&--white {
|
|
||||||
margin: auto auto auto 75%;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--black {
|
|
||||||
margin: auto 75% auto auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.GameButton__player-data {
|
|
||||||
@include gameViewLabel;
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-around;
|
|
||||||
max-width: 100%;
|
|
||||||
|
|
||||||
&.GameButton__player-data--invisible {
|
|
||||||
visibility: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.GameButton__player-data--white {
|
|
||||||
margin: 0 5vw 1vw 0.5vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
&.GameButton__player-data--black {
|
|
||||||
margin: 1vw 0.5vw 0 5vw;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
a.GameButton__link {
|
|
||||||
@include gameViewLabel;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import Game from './Game';
|
|
||||||
|
|
||||||
const gameData = {
|
|
||||||
id: 1,
|
|
||||||
name: "main",
|
|
||||||
description: "A general place to play Go",
|
|
||||||
language: "EN"
|
|
||||||
}
|
|
||||||
|
|
||||||
test('renders GameButton without crashing', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<Game game={gameData} />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const GameButton = getByTestId('GameButton');
|
|
||||||
expect(GameButton).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,34 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import authServices from "../../../services/authServices";
|
|
||||||
|
|
||||||
const Guest = ({ dispatch }) => {
|
|
||||||
const handleClick = async (e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
// dispatch to guest endpoint
|
|
||||||
const guestResponse = await authServices.guestService();
|
|
||||||
|
|
||||||
if (guestResponse.errors) {
|
|
||||||
const authError = guestResponse.errors[0].auth;
|
|
||||||
return dispatch({
|
|
||||||
type: "ERR",
|
|
||||||
message: "AUTH_ERROR",
|
|
||||||
body: { authError },
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return dispatch({
|
|
||||||
type: "AUTH",
|
|
||||||
message: "GUEST",
|
|
||||||
body: guestResponse,
|
|
||||||
});
|
|
||||||
};
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<button className="nav__section__button" onClick={handleClick}>
|
|
||||||
Continue As Guest
|
|
||||||
</button>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Guest;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Library.scss';
|
|
||||||
|
|
||||||
const LibraryButton = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
Library
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default LibraryButton;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './NewGame.scss';
|
|
||||||
|
|
||||||
const NewGameButton = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
New Game
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewGameButton;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './NewRoom.scss';
|
|
||||||
|
|
||||||
const NewRoomButton = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
New Room
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default NewRoomButton;
|
|
|
@ -1,71 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { Link } from 'react-router-dom';
|
|
||||||
import RoomDetail from '../../Display/RoomDetail/RoomDetail';
|
|
||||||
import './Room.scss';
|
|
||||||
|
|
||||||
const RoomButton = ({room, roomDetail, showRoomDetail}) => {
|
|
||||||
const smallRoom = (
|
|
||||||
<div className="small-room">
|
|
||||||
<div className="small-room__roof">
|
|
||||||
<div className="small-room__roof-top small-room__roof-top--left"></div>
|
|
||||||
<div className="small-room__roof--left"></div>
|
|
||||||
<div className="small-room__roof-top small-room__roof-top--right"></div>
|
|
||||||
<div className="small-room__roof--right"></div>
|
|
||||||
</div>
|
|
||||||
<div className="small-room__body">
|
|
||||||
<div className="small-room__trim small-room__trim--left"></div>
|
|
||||||
<div className="small-room__sign">
|
|
||||||
<div className="small-room__sign__stone small-room__sign__stone--black"></div>
|
|
||||||
<div className="small-room__sign__stone small-room__sign__stone--white"></div>
|
|
||||||
<div className="small-room__sign__stone small-room__sign__stone--white"></div>
|
|
||||||
<div className="small-room__sign__stone small-room__sign__stone--black"></div>
|
|
||||||
</div>
|
|
||||||
<div className="small-room__window small-room__window--left">
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--top"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--left"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--middle"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--center"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--right"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--bottom"></div>
|
|
||||||
</div>
|
|
||||||
<div className="small-room__door">
|
|
||||||
<div className="small-room__door-frame small-room__door-frame--top"></div>
|
|
||||||
<div className="small-room__door-frame small-room__door-frame--left"></div>
|
|
||||||
<div className="small-room__door-handle"></div>
|
|
||||||
<div className="small-room__door-frame small-room__door-frame--right"></div>
|
|
||||||
</div>
|
|
||||||
<div className="small-room__window small-room__window--right">
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--top"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--left"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--middle"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--center"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--right"></div>
|
|
||||||
<div className="small-room__window-frame small-room__window-frame--bottom"></div>
|
|
||||||
</div>
|
|
||||||
<div className="small-room__trim small-room__trim--bottom-left"></div>
|
|
||||||
<div className="small-room__trim small-room__trim--right"></div>
|
|
||||||
<div className="small-room__trim small-room__trim--bottom-right"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="RoomButton" data-testid="RoomButton">
|
|
||||||
<h4 className="RoomButton__room-link RoomButton__room-link--action">
|
|
||||||
<Link to={`/rooms/${room.id}`}>Join {room.name}</Link>
|
|
||||||
</h4>
|
|
||||||
<h4
|
|
||||||
className="RoomButton__room-link RoomButton__room-link--info --link"
|
|
||||||
onClick={e => showRoomDetail(room.id)}
|
|
||||||
>
|
|
||||||
?
|
|
||||||
</h4>
|
|
||||||
{smallRoom}
|
|
||||||
</div>
|
|
||||||
{roomDetail ? <RoomDetail room={room} /> : <></>}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RoomButton;
|
|
|
@ -1,227 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
div.RoomButton {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: [left] 1fr [right];
|
|
||||||
grid-template-rows: [top] 1fr [middle] 4fr [bottom];
|
|
||||||
height: 20vh;
|
|
||||||
padding: 1vw;
|
|
||||||
width: 20vh;
|
|
||||||
|
|
||||||
.RoomButton__room-link {
|
|
||||||
@include gameViewLabel;
|
|
||||||
align-self: self-end;
|
|
||||||
grid-area: top/left/middle/right;
|
|
||||||
max-width: fit-content;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&--info {
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.small-room {
|
|
||||||
display: grid;
|
|
||||||
grid-area: top/left/bottom/right;
|
|
||||||
grid-template-rows: [sky-start] 4vh [roof-start] 5vh [body-start] 1fr [body-end];
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.small-room__roof {
|
|
||||||
display: grid;
|
|
||||||
grid-row: roof-start / body-start;
|
|
||||||
grid-template-columns: [left] 10vh [middle] 10vh [right];
|
|
||||||
grid-template-rows: 1fr;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 20vh;
|
|
||||||
|
|
||||||
.small-room__roof-top {
|
|
||||||
background: map-get($small-room, "roof");
|
|
||||||
box-shadow: 0 0.35vh 0.75vh map-get($colors, "home");
|
|
||||||
grid-row: 1 / 2;
|
|
||||||
height: 0.7vh;
|
|
||||||
position: relative;
|
|
||||||
top: 3vh;
|
|
||||||
width: 12vh;
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-column: left / middle;
|
|
||||||
left: -1.2vh;
|
|
||||||
transform: rotate(-26deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
&--right {
|
|
||||||
grid-column: middle / right;
|
|
||||||
left: -0.8vh;
|
|
||||||
transform: rotate(26deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
border-right: map-get($small-room, "body") solid 10vh;
|
|
||||||
border-top: map-get($colors, "home") solid 5vh;
|
|
||||||
grid-column: left / middle;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--right {
|
|
||||||
border-left: map-get($small-room, "body") solid 10vh;
|
|
||||||
border-top: map-get($colors, "home") solid 5vh;
|
|
||||||
grid-column: middle / right;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__body {
|
|
||||||
background-color: map-get($small-room, "body");
|
|
||||||
display: grid;
|
|
||||||
grid-row: body-start / body-end;
|
|
||||||
grid-template-areas:
|
|
||||||
"trim-left gap-left top-left gap-mid-left top-mid gap-mid-right top-right gap-right trim-right"
|
|
||||||
"trim-left gap-left top-left gap-mid-left top-mid gap-mid-right top-right gap-right trim-right"
|
|
||||||
"trim-left gap-left window--left gap-mid-left door gap-mid-right window--right gap-right trim-right"
|
|
||||||
"trim-left trim--bottom-left trim--bottom-left trim--bottom-left door trim--bottom-right trim--bottom-right trim--bottom-right trim-right";
|
|
||||||
grid-template-columns: 0.5vh 1vh 2fr 1vh 3fr 1vh 2fr 1vh 0.5vh;
|
|
||||||
grid-template-rows: 1fr 1vh 5fr 0.75vh;
|
|
||||||
justify-self: center;
|
|
||||||
width: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__trim {
|
|
||||||
background-color: map-get($small-room, "frame");
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-area: trim-left;
|
|
||||||
justify-self: start;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--right {
|
|
||||||
grid-area: trim-right;
|
|
||||||
justify-self: end;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--bottom-left {
|
|
||||||
grid-area: trim--bottom-left;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--bottom-right {
|
|
||||||
grid-area: trim--bottom-right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__sign {
|
|
||||||
background-color: map-get($small-room, "sign");
|
|
||||||
display: grid;
|
|
||||||
gap: 0.1vh;
|
|
||||||
grid-area: top-mid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
grid-template-rows: 1fr 1fr;
|
|
||||||
height: 2vh;
|
|
||||||
justify-self: center;
|
|
||||||
padding: 0.1vh;
|
|
||||||
width: 2vh;
|
|
||||||
|
|
||||||
.small-room__sign__stone {
|
|
||||||
border-radius: 100%;
|
|
||||||
|
|
||||||
&--white {
|
|
||||||
background-color: #FFF;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--black {
|
|
||||||
background-color: #000;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__window {
|
|
||||||
align-self: center;
|
|
||||||
background: map-get($small-room, "window-x"), map-get($small-room, "window-y");
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 5fr 1fr 5fr 1fr;
|
|
||||||
grid-template-rows: 1fr 5fr 1fr 5fr 1fr;
|
|
||||||
height: 60%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-area: window--left;
|
|
||||||
}
|
|
||||||
&--right {
|
|
||||||
grid-area: window--right;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__window-frame {
|
|
||||||
background-color: map-get($small-room, "frame");
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--top {
|
|
||||||
grid-area: 1/1/2/6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--center {
|
|
||||||
grid-area: 3/1/4/6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--bottom {
|
|
||||||
grid-area: 5/1/6/6;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-area: 1/1/6/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--middle {
|
|
||||||
grid-area: 1/3/6/4;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--right {
|
|
||||||
grid-area: 1/5/6/6;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__door {
|
|
||||||
grid-area: door;
|
|
||||||
background: map-get($small-room, "door");
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 0.5vh 1fr 0.5vh;
|
|
||||||
grid-template-rows: 0.5vh 1fr;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
.small-room__door-handle {
|
|
||||||
align-self: center;
|
|
||||||
background: map-get($small-room, "door-handle");
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: 0.1vh 0.25vh 0.4vh rgba(30, 30, 30, 0.7);
|
|
||||||
grid-area: 2/2/3/3;
|
|
||||||
height: 0.75vh;
|
|
||||||
justify-self: end;
|
|
||||||
margin: 0.5vh 0.5vh 0 0;
|
|
||||||
width: 0.75vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.small-room__door-frame {
|
|
||||||
background-color: map-get($small-room, "frame");
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&--top {
|
|
||||||
grid-area: 1/1/2/3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-area: 1/1/3/2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&--left {
|
|
||||||
grid-area: 1/3/3/4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,32 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import Room from './Room';
|
|
||||||
|
|
||||||
const roomData = {
|
|
||||||
id: 1,
|
|
||||||
name: "main",
|
|
||||||
description: "A general place to play Go",
|
|
||||||
language: "EN"
|
|
||||||
}
|
|
||||||
|
|
||||||
test('renders RoomButton without crashing', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<Room room={roomData} />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const RoomButton = getByTestId('RoomButton');
|
|
||||||
expect(RoomButton).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('renders RoomButton with Room name', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<Room room={roomData} />
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const RoomButton = getByTestId('RoomButton');
|
|
||||||
expect(RoomButton).toHaveTextContent('main');
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './RoomArchive.scss';
|
|
||||||
|
|
||||||
const RoomArchiveButton = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
Room Archive
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RoomArchiveButton;
|
|
|
@ -1,9 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
const Development = () => {
|
|
||||||
return (<>
|
|
||||||
<p>This Feature is in Development :)</p>
|
|
||||||
</>);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Development;
|
|
|
@ -1,80 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Loading.scss';
|
|
||||||
|
|
||||||
const Loading = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="Loading"
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="a-1"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="a-2"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="a-3"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="a-4"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="b-1"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="b-2"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="b-3"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="b-4"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="c-1"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="c-2"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="c-3"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="c-4"
|
|
||||||
></div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="d-1"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="d-2"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="d-3"
|
|
||||||
></div>
|
|
||||||
<div
|
|
||||||
className="point"
|
|
||||||
id="d-4"
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Loading;
|
|
|
@ -1,186 +0,0 @@
|
||||||
div.Loading {
|
|
||||||
background-color: rgb(234, 178, 78);
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
height: 6vw;
|
|
||||||
left: 47vw;
|
|
||||||
position: absolute;
|
|
||||||
top: 45vh;
|
|
||||||
width: 6vw;
|
|
||||||
|
|
||||||
div.point {
|
|
||||||
height: 1vw;
|
|
||||||
width: 1vw;
|
|
||||||
border-radius: 50%;
|
|
||||||
padding: 0.25vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
#a-1 {
|
|
||||||
animation-name: white-stone--corner;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -2.75s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#a-2 {
|
|
||||||
background-color: black;
|
|
||||||
animation-name: black-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -1s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#a-3 {
|
|
||||||
background-color: white;
|
|
||||||
animation-name: white-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -2.25s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#a-4 {
|
|
||||||
animation-name: black-stone--corner;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: 0s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#b-1 {
|
|
||||||
background-color: black;
|
|
||||||
animation-name: black-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -1.5s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#b-2 {
|
|
||||||
animation-name: white-stone--center;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -3.25s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#b-3 {
|
|
||||||
background-color: black;
|
|
||||||
animation-name: black-stone--center;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -.5s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#b-4 {
|
|
||||||
background-color: white;
|
|
||||||
animation-name: white-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -1.75s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#c-1 {
|
|
||||||
animation-name: white-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -3.75s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#c-2 {
|
|
||||||
background-color: black;
|
|
||||||
animation-name: black-stone--center;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -2.5s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#c-3 {
|
|
||||||
background-color: white;
|
|
||||||
animation-name: white-stone--center;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -1.25s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#c-4 {
|
|
||||||
animation-name: black-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -3.5s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
#d-1 {
|
|
||||||
background-color: black;
|
|
||||||
animation-name: black-stone--corner;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -2s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#d-2 {
|
|
||||||
background-color: white;
|
|
||||||
animation-name: white-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -.25s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#d-3 {
|
|
||||||
animation-name: black-stone;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -3s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
#d-4 {
|
|
||||||
background-color: white;
|
|
||||||
animation-name: white-stone--corner;
|
|
||||||
animation-duration: 4s;
|
|
||||||
animation-delay: -.75s;
|
|
||||||
animation-iteration-count: infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes black-stone {
|
|
||||||
0% {background: black;}
|
|
||||||
67.5% {background: black;}
|
|
||||||
70% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);}
|
|
||||||
99.5% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes white-stone {
|
|
||||||
0% {background: white;}
|
|
||||||
60% {background: white;}
|
|
||||||
62.5% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);}
|
|
||||||
92.5% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);}
|
|
||||||
95% {background: white;}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes black-stone--corner {
|
|
||||||
0% {background: black;}
|
|
||||||
52.5% {background: black;};
|
|
||||||
55% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
99% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes white-stone--corner {
|
|
||||||
0% {background: white;}
|
|
||||||
47.5% {background: white;};
|
|
||||||
50% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
95% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
97.5% {background: white;}
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes black-stone--center {
|
|
||||||
0% {background: black;}
|
|
||||||
75.5% {background: black;};
|
|
||||||
77.5% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
99% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes white-stone--center {
|
|
||||||
0% {background: white;}
|
|
||||||
72.5% {background: white;};
|
|
||||||
75% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
97.5% {background: repeating-linear-gradient(0deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%),
|
|
||||||
repeating-linear-gradient(90deg, transparent 0%, transparent 45%, black 50%, transparent 55%, transparent 100%);};
|
|
||||||
100% {background: white;}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Logo.scss';
|
|
||||||
|
|
||||||
const Logo = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
Logo
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Logo;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Message.scss';
|
|
||||||
|
|
||||||
const Message = (props) => {
|
|
||||||
const messageData = props.message;
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="Message"
|
|
||||||
data-testid="Message"
|
|
||||||
>
|
|
||||||
<p>{messageData.content}</p>
|
|
||||||
<p>{messageData.username}</p>
|
|
||||||
<p>{messageData.admin ? 'admin' : <></>}</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Message;
|
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import Message from './Message';
|
|
||||||
|
|
||||||
const messageData = [
|
|
||||||
{
|
|
||||||
"content": "Hey! Welcome to the general room!", "username": "userOne", "admin": true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
test('renders Message without crashing', () => {
|
|
||||||
const { getByTestId } = render( <Message message={messageData} /> );
|
|
||||||
const messageDiv = getByTestId('Message');
|
|
||||||
expect(messageDiv).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
|
@ -1,25 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './RoomDetail.scss';
|
|
||||||
|
|
||||||
const RoomDetail = ({room}) => {
|
|
||||||
return (
|
|
||||||
<div className="RoomDetail">
|
|
||||||
<div className="RoomDetail__arrow"></div>
|
|
||||||
<div className="RoomDetail__data">
|
|
||||||
<div className="RoomDetail__heading">
|
|
||||||
<h3 className="RoomDetail__room-name">{room.name}</h3>
|
|
||||||
<div className="RoomDetail__room-lang"></div>
|
|
||||||
<p className="RoomDetail__room-description">{room.description}</p>
|
|
||||||
</div>
|
|
||||||
<div className="RoomDetail__current">
|
|
||||||
<h4 className="RoomDetail__current-header">Current:</h4>
|
|
||||||
<p className="RoomDetail__current-players"></p>
|
|
||||||
<p className="RoomDetail__current-games"></p>
|
|
||||||
<p className="RoomDetail__current-rank"></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RoomDetail;
|
|
|
@ -1,32 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
div.RoomDetail {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: [left] 1fr [right];
|
|
||||||
grid-template-rows: [top] 1fr [middle] 9fr [bottom];
|
|
||||||
height: 20vh;
|
|
||||||
padding: 1vw;
|
|
||||||
width: 20vh;
|
|
||||||
|
|
||||||
div.RoomDetail__arrow {
|
|
||||||
border-top: map-get($colors, 'home') solid 2vh;
|
|
||||||
border-left: white solid 1.5vh;
|
|
||||||
height: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.RoomDetail__data {
|
|
||||||
background-color: white;
|
|
||||||
height: 90%;
|
|
||||||
width: 100%;
|
|
||||||
padding: 1vh;
|
|
||||||
display: grid;
|
|
||||||
grid-template-rows: 2fr 3fr;
|
|
||||||
|
|
||||||
.RoomDetail__heading {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
align-items: flex-start;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './ActionError.scss';
|
|
||||||
|
|
||||||
const ActionError = (props) => {
|
|
||||||
const errorMessage = props.error || '';
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
className="error error--action"
|
|
||||||
data-testid="ActionError"
|
|
||||||
>
|
|
||||||
{errorMessage}
|
|
||||||
</span>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default ActionError;
|
|
|
@ -1,5 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
span.FormError {
|
|
||||||
color: map-get($colors, "error");;
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import ActionError from './ActionError';
|
|
||||||
|
|
||||||
test('renders ActionError without crashing', () => {
|
|
||||||
const { getByTestId } = render(<ActionError />);
|
|
||||||
const ActionErrorSpan = getByTestId('ActionError');
|
|
||||||
expect(ActionErrorSpan).toBeInTheDocument();
|
|
||||||
});
|
|
||||||
|
|
||||||
test('renders ActionError with error message', () => {
|
|
||||||
const errorMessage = "User already exists!";
|
|
||||||
const { getByTestId } = render(<ActionError error={errorMessage}/>);
|
|
||||||
const ActionErrorSpan = getByTestId('ActionError');
|
|
||||||
expect(ActionErrorSpan).toHaveTextContent(errorMessage);
|
|
||||||
|
|
||||||
})
|
|
|
@ -1,5 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
span.FormError {
|
|
||||||
color: map-get($colors, "error");;
|
|
||||||
}
|
|
|
@ -1,49 +0,0 @@
|
||||||
import React, { useState } from "react";
|
|
||||||
|
|
||||||
import Login from "../Login/Login";
|
|
||||||
import Signup from "../Signup/Signup";
|
|
||||||
import Guest from "../../Button/Guest/Guest";
|
|
||||||
|
|
||||||
const Auth = (props) => {
|
|
||||||
const [showForm, setShowForm] = useState("login");
|
|
||||||
const { state, dispatch } = props;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div
|
|
||||||
className="nav__section nav__section--auth"
|
|
||||||
onClick={() => {
|
|
||||||
setShowForm("login");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p className="nav__section__label">Login</p>
|
|
||||||
|
|
||||||
{showForm === "login" ? (
|
|
||||||
<Login dispatch={dispatch} state={state} />
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="nav__section nav__section--auth"
|
|
||||||
onClick={() => {
|
|
||||||
setShowForm("signup");
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<p className="nav__section__label">Signup</p>
|
|
||||||
|
|
||||||
{showForm === "signup" ? (
|
|
||||||
<Signup dispatch={dispatch} state={state} />
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
<div className="nav__section nav__section--auth">
|
|
||||||
<Guest dispatch={dispatch} />
|
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Auth;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './FindGame.scss';
|
|
||||||
|
|
||||||
const FindGameForm = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
Find Game
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FindGameForm;
|
|
|
@ -1,18 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './FindRoom.scss';
|
|
||||||
|
|
||||||
const FindRoomForm = () => {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className="nav__section"
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className="nav__section__label"
|
|
||||||
>
|
|
||||||
Find Room
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FindRoomForm;
|
|
|
@ -1,74 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "./Board.scss";
|
|
||||||
import Point from "../Point/Point";
|
|
||||||
|
|
||||||
const Board = (props) => {
|
|
||||||
const { game, user, dispatch, board, meta } = props;
|
|
||||||
const sizeFlag = `Game__board--size-${game.boardSize}`;
|
|
||||||
const hoshiPoints = {
|
|
||||||
9: { "3-3": true, "7-7": true, "3-7": true, "7-3": true },
|
|
||||||
13: {
|
|
||||||
"7-7": true,
|
|
||||||
"10-7": true,
|
|
||||||
"7-4": true,
|
|
||||||
"7-10": true,
|
|
||||||
"4-7": true,
|
|
||||||
"4-4": true,
|
|
||||||
"10-10": true,
|
|
||||||
"4-10": true,
|
|
||||||
"10-4": true,
|
|
||||||
},
|
|
||||||
19: {
|
|
||||||
"10-10": true,
|
|
||||||
"16-10": true,
|
|
||||||
"10-4": true,
|
|
||||||
"10-16": true,
|
|
||||||
"4-10": true,
|
|
||||||
"4-4": true,
|
|
||||||
"16-16": true,
|
|
||||||
"4-16": true,
|
|
||||||
"16-4": true,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const isHoshi = (posX, posY) =>
|
|
||||||
hoshiPoints[game.boardSize][`${posX}-${posY}`];
|
|
||||||
|
|
||||||
const renderPoints = (boardSize) => {
|
|
||||||
let i = 0,
|
|
||||||
boardPoints = [];
|
|
||||||
while (i < boardSize * boardSize) {
|
|
||||||
const posX = Math.floor(i / boardSize) + 1;
|
|
||||||
const posY = (i % boardSize) + 1;
|
|
||||||
const pointData = board[`${posX}-${posY}`];
|
|
||||||
const dotData =
|
|
||||||
meta && meta.turn === 0 && !meta.winner && meta.territory
|
|
||||||
? meta.territory[`${posX}-${posY}`]
|
|
||||||
: game.turn || meta?.turn;
|
|
||||||
boardPoints.push(
|
|
||||||
<Point
|
|
||||||
key={`${posX}-${posY}`}
|
|
||||||
posX={posX}
|
|
||||||
posY={posY}
|
|
||||||
pointData={pointData}
|
|
||||||
dotData={dotData}
|
|
||||||
dispatch={dispatch}
|
|
||||||
user={user}
|
|
||||||
meta={meta}
|
|
||||||
hoshi={isHoshi(posX, posY)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
i++;
|
|
||||||
}
|
|
||||||
return boardPoints;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`Game__board ${sizeFlag}`}>
|
|
||||||
{game.id ? renderPoints(game.boardSize) : <></>}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Board;
|
|
|
@ -1,61 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
div.Game__board {
|
|
||||||
// @include fullspan;
|
|
||||||
background: radial-gradient(farthest-corner at 55% 40%, rgb(244, 230, 120) 0%, rgb(234, 178, 78) 65%, rgb(200, 160, 90) 90%, rgb(200, 140, 90) 100%);
|
|
||||||
background-size: cover;
|
|
||||||
box-shadow: -2vmin 4vmin 3vmin rgba(145, 92, 23, 0.5);
|
|
||||||
display: grid;
|
|
||||||
// flex: 1;
|
|
||||||
margin: 0 auto;
|
|
||||||
z-index: 1;
|
|
||||||
|
|
||||||
&--size-9 {
|
|
||||||
grid-template-columns: repeat(9, 1fr);
|
|
||||||
grid-template-rows: repeat(9, 1fr);
|
|
||||||
|
|
||||||
div.board__point {
|
|
||||||
width: 9vmin;
|
|
||||||
height: 9vmin;
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "570")} {
|
|
||||||
height: 7.5vh;
|
|
||||||
width: 7.5vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--size-13 {
|
|
||||||
grid-template-columns: repeat(13, 1fr);
|
|
||||||
grid-template-rows: repeat(13, 1fr);
|
|
||||||
|
|
||||||
div.board__point {
|
|
||||||
width: 7vmin;
|
|
||||||
height: 7vmin;
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "570")} {
|
|
||||||
height: 5vh;
|
|
||||||
width: 5vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&--size-19 {
|
|
||||||
grid-template-columns: repeat(19, 1fr);
|
|
||||||
grid-template-rows: repeat(19, 1fr);
|
|
||||||
|
|
||||||
div.board__point {
|
|
||||||
width: 5vmin;
|
|
||||||
height: 5vmin;
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "590")} {
|
|
||||||
height: 2.6vw;
|
|
||||||
width: 2.6vw;
|
|
||||||
max-width: 3.5vh;
|
|
||||||
max-height: 3.5vh;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './GameTree.scss';
|
|
||||||
|
|
||||||
const GameTree = () => {
|
|
||||||
return (
|
|
||||||
<div className="GameTree">
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default GameTree;
|
|
|
@ -1,15 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "./Kifu.scss";
|
|
||||||
|
|
||||||
const Kifu = ({ clickKifu }) => {
|
|
||||||
return (
|
|
||||||
<div className="Kifu">
|
|
||||||
<p className="Kifu__show-menu" onClick={clickKifu}>
|
|
||||||
Show Menu?
|
|
||||||
</p>
|
|
||||||
<div className="Kifu__board"></div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Kifu;
|
|
|
@ -1,31 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
|
|
||||||
div.Kifu {
|
|
||||||
order: 0;
|
|
||||||
height: 10vh;
|
|
||||||
width: 8vh;
|
|
||||||
background-color: #FFF;
|
|
||||||
transform: rotate(-20deg);
|
|
||||||
|
|
||||||
p.Kifu__show-menu {
|
|
||||||
display: none;
|
|
||||||
margin: 3vh auto;
|
|
||||||
position: absolute;
|
|
||||||
transform: rotate(20deg);
|
|
||||||
width: fit-content;
|
|
||||||
}
|
|
||||||
&:hover {
|
|
||||||
p.Kifu__show-menu {
|
|
||||||
@include gameViewLabel;
|
|
||||||
cursor: pointer;
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.Kifu__board {
|
|
||||||
border: 0.25vh solid #444;
|
|
||||||
width: 5vh;
|
|
||||||
height: 5vh;
|
|
||||||
margin: 1vh 2vh 2vh auto;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,194 +0,0 @@
|
||||||
import React, { useEffect, useRef } from "react";
|
|
||||||
import "./Menu.scss";
|
|
||||||
|
|
||||||
const Menu = ({ showMenu, clickClose, ...props }) => {
|
|
||||||
const { active, meta } = props.state; // active.game.boardSize, meta.gameRecord
|
|
||||||
const boardSize = active.game.boardSize;
|
|
||||||
const handleBackgroundClick = (e) => {
|
|
||||||
if (e.target.className === "Game__Menu-container") clickClose();
|
|
||||||
};
|
|
||||||
const canvasRef = useRef();
|
|
||||||
const overflowRef = useRef();
|
|
||||||
const drawGameRecord = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<canvas ref={canvasRef} />
|
|
||||||
<canvas ref={overflowRef} />
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const clickPrint = () => {
|
|
||||||
const iframe = document.createElement("iframe");
|
|
||||||
const body = document.getElementsByTagName("body")[0];
|
|
||||||
const record = document.getElementById("record");
|
|
||||||
body.appendChild(iframe);
|
|
||||||
// move game record to iframe and print iframe
|
|
||||||
iframe.contentDocument.body.appendChild(canvasRef.current);
|
|
||||||
iframe.contentDocument.body.appendChild(overflowRef.current);
|
|
||||||
iframe.contentWindow.print();
|
|
||||||
// move game record back to menu and remove iframe
|
|
||||||
record.appendChild(canvasRef.current);
|
|
||||||
record.appendChild(overflowRef.current);
|
|
||||||
body.removeChild(iframe);
|
|
||||||
};
|
|
||||||
|
|
||||||
// draw canvases
|
|
||||||
useEffect(() => {
|
|
||||||
const canvas = canvasRef.current;
|
|
||||||
const ctx = canvas.getContext("2d");
|
|
||||||
const scale = Math.min(window.innerWidth * 0.75, 500);
|
|
||||||
canvas.height = scale;
|
|
||||||
canvas.width = scale;
|
|
||||||
const space = scale / boardSize;
|
|
||||||
const offset = space / 2;
|
|
||||||
for (let i = 0; i < boardSize; i++) {
|
|
||||||
const start = i * space + offset;
|
|
||||||
const end = scale - offset;
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(start, offset);
|
|
||||||
ctx.lineTo(start, end);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.moveTo(offset, start);
|
|
||||||
ctx.lineTo(end, start);
|
|
||||||
ctx.closePath();
|
|
||||||
ctx.stroke();
|
|
||||||
}
|
|
||||||
if (!meta?.gameRecord) return;
|
|
||||||
ctx.textAlign = "center";
|
|
||||||
ctx.textBaseline = "middle";
|
|
||||||
const { overflow } = meta.gameRecord.reduce(
|
|
||||||
(dict, { player, pos }, index) => {
|
|
||||||
const past = dict[`${pos.x}-${pos.y}`];
|
|
||||||
if (past) {
|
|
||||||
// overflow: [ { move:#, player:'color', subsequentMoves: [ { move: #, player: 'color' } ] } ]
|
|
||||||
if (dict.overflow) {
|
|
||||||
const indexOfPrior = dict.overflow.findIndex(
|
|
||||||
({ move }) => move === past
|
|
||||||
);
|
|
||||||
if (indexOfPrior !== -1) {
|
|
||||||
// if multiple past moves at this point exist
|
|
||||||
dict.overflow[indexOfPrior].subsequentMoves.push({
|
|
||||||
move: index + 1,
|
|
||||||
player,
|
|
||||||
});
|
|
||||||
return dict;
|
|
||||||
}
|
|
||||||
// if a second move at this point has not yet been encountered
|
|
||||||
// prior move will be black if no active handicap and move is odd or if active handicap and move is even
|
|
||||||
const playerPrior =
|
|
||||||
(active.handicap && !(past % 2)) || past % 2 ? "black" : "white";
|
|
||||||
return {
|
|
||||||
...dict,
|
|
||||||
overflow: [
|
|
||||||
...dict.overflow,
|
|
||||||
{
|
|
||||||
move: past,
|
|
||||||
player: playerPrior,
|
|
||||||
subsequentMoves: [{ move: index + 1, player }],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
// if no move has yet been encountered at a previously made move
|
|
||||||
return {
|
|
||||||
...dict,
|
|
||||||
overflow: [
|
|
||||||
{ move: past, subsequentMoves: [{ move: index + 1, player }] },
|
|
||||||
],
|
|
||||||
};
|
|
||||||
}
|
|
||||||
ctx.beginPath();
|
|
||||||
ctx.arc(
|
|
||||||
(pos.y - 1) * space + offset,
|
|
||||||
(pos.x - 1) * space + offset,
|
|
||||||
offset * 0.95,
|
|
||||||
0,
|
|
||||||
Math.PI * 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
ctx.stroke();
|
|
||||||
ctx.fillStyle = player === "white" ? "#fff" : "#000";
|
|
||||||
ctx.fill();
|
|
||||||
ctx.fillStyle = player === "white" ? "#000" : "#fff";
|
|
||||||
|
|
||||||
ctx.fillText(
|
|
||||||
index + 1,
|
|
||||||
(pos.y - 1) * space + offset,
|
|
||||||
(pos.x - 1) * space + offset
|
|
||||||
);
|
|
||||||
return { ...dict, [`${pos.x}-${pos.y}`]: index + 1 };
|
|
||||||
},
|
|
||||||
{}
|
|
||||||
);
|
|
||||||
if (!overflow?.length) return;
|
|
||||||
// Draw Overflow Moves (moves made at prior points)
|
|
||||||
const canvas2 = overflowRef.current;
|
|
||||||
const ctx2 = canvas2.getContext("2d");
|
|
||||||
canvas2.width = scale;
|
|
||||||
canvas2.height = space * overflow.length;
|
|
||||||
ctx2.textAlign = "center";
|
|
||||||
ctx2.textBaseline = "middle";
|
|
||||||
overflow.forEach(({ move, subsequentMoves, player }, index) => {
|
|
||||||
subsequentMoves.forEach(({ player, move }, subIndex) => {
|
|
||||||
ctx2.beginPath();
|
|
||||||
ctx2.arc(
|
|
||||||
subIndex * space + offset,
|
|
||||||
index * space + offset,
|
|
||||||
offset * 0.95,
|
|
||||||
0,
|
|
||||||
Math.PI * 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
ctx2.stroke();
|
|
||||||
ctx2.fillStyle = player === "white" ? "#fff" : "#000";
|
|
||||||
ctx2.fill();
|
|
||||||
ctx2.fillStyle = player === "white" ? "#000" : "#fff";
|
|
||||||
ctx2.fillText(move, subIndex * space + offset, index * space + offset);
|
|
||||||
});
|
|
||||||
ctx2.fillStyle = "#000";
|
|
||||||
ctx2.fillText(
|
|
||||||
"at",
|
|
||||||
subsequentMoves.length * space + offset,
|
|
||||||
index * space + offset
|
|
||||||
);
|
|
||||||
ctx2.fillStyle = player === "white" ? "#fff" : "#000";
|
|
||||||
ctx2.beginPath();
|
|
||||||
ctx2.arc(
|
|
||||||
(subsequentMoves.length + 1) * space + offset,
|
|
||||||
index * space + offset,
|
|
||||||
offset * 0.95,
|
|
||||||
0,
|
|
||||||
Math.PI * 2,
|
|
||||||
true
|
|
||||||
);
|
|
||||||
ctx2.fill();
|
|
||||||
ctx2.stroke();
|
|
||||||
ctx2.fillStyle = player === "white" ? "#000" : "#fff";
|
|
||||||
ctx2.fillText(
|
|
||||||
move,
|
|
||||||
(subsequentMoves.length + 1) * space + offset,
|
|
||||||
index * space + offset
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}, [showMenu, meta, active.handicap, boardSize]);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`Game__Menu-container${showMenu ? "" : "--hidden"}`}
|
|
||||||
onClick={(e) => handleBackgroundClick(e)}
|
|
||||||
>
|
|
||||||
<div className="Game__Menu-container__Menu">
|
|
||||||
<button onClick={clickClose}>X</button>
|
|
||||||
<button onClick={clickPrint}>Print Record</button>
|
|
||||||
<div id="record" className="Game__Menu__game-record-container">
|
|
||||||
{drawGameRecord()}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Menu;
|
|
|
@ -1,29 +0,0 @@
|
||||||
div.Game__Menu-container {
|
|
||||||
&--hidden {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
background: rgba(0,0,0,0.5);
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
height: 100vh;
|
|
||||||
position: absolute;
|
|
||||||
width: 100vw;
|
|
||||||
z-index: 2;
|
|
||||||
|
|
||||||
.Game__Menu-container__Menu {
|
|
||||||
background: #eef;
|
|
||||||
max-height: 90vh;
|
|
||||||
max-width: 80vw;
|
|
||||||
padding: 2vh;
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.Game__Menu__game-record-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './MessageContainer.scss';
|
|
||||||
|
|
||||||
const MessageContainer = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MessageContainer;
|
|
|
@ -1,66 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "./PlayerArea.scss";
|
|
||||||
|
|
||||||
const PlayerArea = ({
|
|
||||||
handleResignClick,
|
|
||||||
handlePassClick,
|
|
||||||
playerMeta,
|
|
||||||
turn,
|
|
||||||
Kifu,
|
|
||||||
}) => {
|
|
||||||
const { stones, player, rank, captures } = playerMeta;
|
|
||||||
const isTurn =
|
|
||||||
(stones === "black" && turn === 1) || (stones === "white" && turn === -1);
|
|
||||||
const bowlAttributes = () => {
|
|
||||||
if (isTurn || turn === 0)
|
|
||||||
return {
|
|
||||||
"data-turn": true,
|
|
||||||
onClick: () => handlePassClick(stones),
|
|
||||||
};
|
|
||||||
return null;
|
|
||||||
};
|
|
||||||
const bowlText = () => {
|
|
||||||
if (isTurn) return "Pass?";
|
|
||||||
if (turn === 0) return "End Game?";
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className={`player-container player-container--${stones}`}>
|
|
||||||
<div
|
|
||||||
className={`player-container__bowl player-container__bowl--${stones}`}
|
|
||||||
{...bowlAttributes()}
|
|
||||||
>
|
|
||||||
<p>{bowlText()}</p>
|
|
||||||
</div>
|
|
||||||
{Kifu}
|
|
||||||
<div
|
|
||||||
className={`player-container__name-space player-container__name-space--${stones}`}
|
|
||||||
>
|
|
||||||
<h4>
|
|
||||||
{playerMeta
|
|
||||||
? `${player || stones} ${rank || "?"}`
|
|
||||||
: "Waiting for player"}
|
|
||||||
</h4>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={`player-container__caps-space player-container__caps-space__${stones}`}
|
|
||||||
>
|
|
||||||
<p
|
|
||||||
className={`player-container__resign-message player-container__resign-message--${stones}`}
|
|
||||||
{...(isTurn ? { onClick: () => handleResignClick(stones) } : null)}
|
|
||||||
>
|
|
||||||
Resign?
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p
|
|
||||||
className={`player-container__caps-counter player-container__caps-counter--${stones}`}
|
|
||||||
>
|
|
||||||
{playerMeta ? captures : "Captures go here"}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default PlayerArea;
|
|
|
@ -1,102 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
|
|
||||||
div.player-container {
|
|
||||||
align-items: flex-end;
|
|
||||||
display: flex;
|
|
||||||
flex: 5;
|
|
||||||
height: 9vmin;
|
|
||||||
justify-content: space-around;
|
|
||||||
margin: 1em;
|
|
||||||
width: 100%;
|
|
||||||
|
|
||||||
&:last-child {
|
|
||||||
align-items: flex-start;
|
|
||||||
flex-direction: row-reverse;
|
|
||||||
justify-self: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "500")} {
|
|
||||||
height: 14vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.player-container__bowl {
|
|
||||||
align-items: center;
|
|
||||||
background: radial-gradient(farthest-corner at 48% 54%, rgba(30, 5, 0, 0.25) 0%, rgba(30, 5, 0, 0.45) 2%, rgba(30, 5, 0, 0.75) 32%, rgba(0,0,0,0.85)35%, rgb(116,48,17) 48%, rgb(140, 60, 40) 52%, rgb(100, 40, 5) 55%, rgb(116, 48, 17) 58%, rgb(140,60,40) 65%, rgb(100, 40, 5) 80%, rgb(80, 20, 0) 90%);
|
|
||||||
background-color: rgb(116, 48, 17);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: -1vmin 2vmin 1.5vmin rgba(83, 53, 35, 0.61);
|
|
||||||
display: flex;
|
|
||||||
height: 15vh;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 4vh;
|
|
||||||
order: -1;
|
|
||||||
width: 15vh;
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "570")} {
|
|
||||||
height: 10vh;
|
|
||||||
margin: 3vh;
|
|
||||||
width: 10vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-turn]:hover p {
|
|
||||||
background-color: rgba(0,0,0,0.3);
|
|
||||||
color: #FFF;
|
|
||||||
cursor: grab;
|
|
||||||
display: block;
|
|
||||||
padding: .5em;
|
|
||||||
}
|
|
||||||
&[data-turn] {
|
|
||||||
// highlight for turn
|
|
||||||
box-shadow: 0 0 3vh 3vh rgb(255, 175, 2);
|
|
||||||
|
|
||||||
& + .player-container__name-space .player-container__caps-space:hover :first-child {
|
|
||||||
background-color: rgba(0,0,0,0.7);
|
|
||||||
cursor: grab;
|
|
||||||
display: block;
|
|
||||||
padding: .5em;
|
|
||||||
position: absolute;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
p {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.player-container__caps-space {
|
|
||||||
align-items: center;
|
|
||||||
background: radial-gradient(farthest-side at 49% 52%, rgb(150, 75, 50) 0%, rgb(116,48,17) 35%, rgb(116,48,17) 64%, rgb(80, 20, 0) 65%, rgb(175, 140, 95) 70%, rgb(120, 50, 40) 80%, rgb(80, 20, 0) 95%, rgb(175, 140, 95) 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
box-shadow: -0.5vmin 1vmin 1vmin rgba(83, 53, 35, 0.61);
|
|
||||||
color: #FFF;
|
|
||||||
display: flex;
|
|
||||||
height: 10vh;
|
|
||||||
justify-content: center;
|
|
||||||
margin: 1vh;
|
|
||||||
width: 10vh;
|
|
||||||
|
|
||||||
@media #{map-get($break-points, "570")} {
|
|
||||||
height: 7vh;
|
|
||||||
margin: 2vh;
|
|
||||||
width: 7vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
& :first-child {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.player-container__name-space {
|
|
||||||
align-items: center;
|
|
||||||
flex-direction: column;
|
|
||||||
display: flex;
|
|
||||||
order: 1;
|
|
||||||
|
|
||||||
h4 {
|
|
||||||
@include gameViewLabel;
|
|
||||||
font-size: 120%;
|
|
||||||
z-index: 1;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,96 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import "./Point.scss";
|
|
||||||
|
|
||||||
const Point = (props) => {
|
|
||||||
const {
|
|
||||||
posX,
|
|
||||||
posY,
|
|
||||||
user,
|
|
||||||
game,
|
|
||||||
meta,
|
|
||||||
dispatch,
|
|
||||||
pointData,
|
|
||||||
dotData,
|
|
||||||
hoshi,
|
|
||||||
} = props;
|
|
||||||
const turn =
|
|
||||||
meta && meta.turn
|
|
||||||
? meta.turn > 0
|
|
||||||
? "black"
|
|
||||||
: "white"
|
|
||||||
: game.turn > 0
|
|
||||||
? "black"
|
|
||||||
: "white";
|
|
||||||
|
|
||||||
const stone = () => {
|
|
||||||
if (pointData === 1) return "black";
|
|
||||||
if (pointData === -1) return "white";
|
|
||||||
if (pointData === "k") return "ko";
|
|
||||||
return "none";
|
|
||||||
};
|
|
||||||
|
|
||||||
const xFlag = () => {
|
|
||||||
if (posX === 1) return `board__point--top`;
|
|
||||||
if (posX === game.boardSize) return `board__point--bottom`;
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
const yFlag = () => {
|
|
||||||
if (posY === 1) return `board__point--left`;
|
|
||||||
if (posY === game.boardSize) return `board__point--right`;
|
|
||||||
return "";
|
|
||||||
};
|
|
||||||
const clickHandle = (e) => {
|
|
||||||
if (meta?.turn === 0 && !meta?.winner) {
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "TOGGLE_TERRITORY",
|
|
||||||
body: { user, point: `${posX}-${posY}`, game, room: game.room },
|
|
||||||
};
|
|
||||||
return dispatch(action);
|
|
||||||
}
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "MAKE_MOVE",
|
|
||||||
body: {
|
|
||||||
user,
|
|
||||||
game,
|
|
||||||
room: game.room,
|
|
||||||
board: {},
|
|
||||||
move: { player: turn, pos: { x: posX, y: posY } },
|
|
||||||
},
|
|
||||||
};
|
|
||||||
dispatch(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getDot = () => {
|
|
||||||
if (meta?.turn === 0) {
|
|
||||||
switch (dotData) {
|
|
||||||
case -1:
|
|
||||||
return "white";
|
|
||||||
case 1:
|
|
||||||
return "black";
|
|
||||||
case "d":
|
|
||||||
return "dame";
|
|
||||||
default:
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return dotData;
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={`board__point ${xFlag()} ${yFlag()}`}
|
|
||||||
onClick={(e) => clickHandle(e)}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={`board__point__stone ${hoshi ? "hoshi" : ""}`}
|
|
||||||
data-stone={stone()}
|
|
||||||
>
|
|
||||||
<div className="board__point__dot" data-dot={getDot()}></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Point;
|
|
|
@ -1,107 +0,0 @@
|
||||||
div.board__point {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
border-radius: 50% solid black;
|
|
||||||
color: black;
|
|
||||||
margin: auto;
|
|
||||||
padding: 0;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--top {
|
|
||||||
background: conic-gradient( rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--bottom {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--left {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--right {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--top.board__point--left {
|
|
||||||
background: conic-gradient( rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--top.board__point--right {
|
|
||||||
background: conic-gradient( rgba(0,0,0,0) 49%, #000 50%, rgba(0,0,0,0) 51%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76% );
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--bottom.board__point--left {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 24%, #000 25%, rgba(0,0,0,0) 26%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point--bottom.board__point--right {
|
|
||||||
background: conic-gradient(#000 0%, rgba(0,0,0,0) 1%, rgba(0,0,0,0) 74%, #000 75%, rgba(0,0,0,0) 76%, rgba(0,0,0,0) 99%, #000 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point__stone {
|
|
||||||
width: 85%;
|
|
||||||
height: 85%;
|
|
||||||
border-radius: 50%;
|
|
||||||
vertical-align: middle;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
&.hoshi {
|
|
||||||
background: radial-gradient(circle farthest-corner at center, #000 0%, #000 14%, rgba(0,0,0,0) 15%);
|
|
||||||
z-index: 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point div.board__point__stone div.board__point__dot {
|
|
||||||
width: 35%;
|
|
||||||
height: 35%;
|
|
||||||
border-radius: 50%;
|
|
||||||
margin: auto;
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point__stone{
|
|
||||||
&[data-stone="white"] {
|
|
||||||
background: radial-gradient(farthest-side at 55% 40%, white 0%, rgb(200,200,200) 65%, rgb(100,100,100) 90%, rgb(68, 50, 0) 100%);
|
|
||||||
box-shadow: -.25vmin .5vmin .5vmin rgba(145, 92, 23, 0.5);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-stone="black"] {
|
|
||||||
background-color: black;
|
|
||||||
background: radial-gradient(farthest-side at 55% 40%, rgb(220,220,220) 0%, rgb(60,60,60) 45%, rgb(15,15,15) 90%, rgb(5, 5, 0) 100%);
|
|
||||||
box-shadow: -.25vmin .5vmin .5vmin rgba(145, 92, 23, 0.75);
|
|
||||||
}
|
|
||||||
|
|
||||||
&[data-stone="ko"] {
|
|
||||||
background-color: transparent;
|
|
||||||
border: .5vmin solid rgba(200,20,50,0.8);
|
|
||||||
border-radius: 0%;
|
|
||||||
height: 60%;
|
|
||||||
width: 60%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.board__point {
|
|
||||||
div.board__point__dot[data-dot="dame"] {
|
|
||||||
background: purple;
|
|
||||||
}
|
|
||||||
div.board__point__dot[data-dot="black"] {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
div.board__point__dot[data-dot="white"] {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
div.board__point__dot[data-dot="1"] {
|
|
||||||
background: black;
|
|
||||||
}
|
|
||||||
div.board__point__dot[data-dot="-1"] {
|
|
||||||
background: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,11 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Timer.scss';
|
|
||||||
|
|
||||||
const Timer = () => {
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Timer;
|
|
|
@ -1,56 +0,0 @@
|
||||||
import socketIOClient from "socket.io-client";
|
|
||||||
import config from "./config";
|
|
||||||
|
|
||||||
const launch = (nsp, dispatch) => {
|
|
||||||
const socket = socketIOClient(`${config.socketAddress}/${nsp}`);
|
|
||||||
|
|
||||||
socket.on("connected", () => {
|
|
||||||
dispatch({
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "CONNECTED",
|
|
||||||
body: { nsp: socket.nsp },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
socket.on("connect_error", (err) => {
|
|
||||||
dispatch({
|
|
||||||
type: "ERR",
|
|
||||||
message: "SOCKET_ERROR",
|
|
||||||
body: { socketError: err },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
socket.on("error", (err) => {
|
|
||||||
dispatch({
|
|
||||||
type: "ERR",
|
|
||||||
message: "SOCKET_ERROR",
|
|
||||||
body: { socketError: err },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("room_connected", (data) => {
|
|
||||||
dispatch({ type: "ROOMS", message: "CONNECT_ROOM", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("new_user", (data) => {
|
|
||||||
dispatch({ type: "ROOMS", message: "NEW_USER", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("game_connected", (data) => {
|
|
||||||
dispatch({ type: "GAMES", message: "UPDATE_BOARD", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("update_board", (data) => {
|
|
||||||
dispatch({ type: "GAMES", message: "UPDATE_BOARD", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("game_resign", (data) => {
|
|
||||||
dispatch({ type: "GAMES", message: "GAME_RESIGN", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on("end_game", (data) => {
|
|
||||||
dispatch({ type: "GAMES", message: "GAME_END", body: data });
|
|
||||||
});
|
|
||||||
|
|
||||||
return socket;
|
|
||||||
};
|
|
||||||
|
|
||||||
export { launch };
|
|
|
@ -1,163 +0,0 @@
|
||||||
import React, { useEffect, useState } from "react";
|
|
||||||
import { useParams } from "react-router-dom";
|
|
||||||
import gamesServices from "../../services/api/gamesServices";
|
|
||||||
import "./Game.scss";
|
|
||||||
import Logo from "../../components/Display/Logo/Logo";
|
|
||||||
import Board from "../../components/GameUI/Board/Board";
|
|
||||||
import PlayerArea from "../../components/GameUI/PlayerArea/PlayerArea";
|
|
||||||
import Kifu from "../../components/GameUI/Kifu/Kifu";
|
|
||||||
import Menu from "../../components/GameUI/Menu/Menu";
|
|
||||||
|
|
||||||
const Game = (props) => {
|
|
||||||
const { state, dispatch } = props;
|
|
||||||
const gameId = parseInt(useParams().id) || 0;
|
|
||||||
const [playerBlackMeta, setPlayerBlackMeta] = useState({});
|
|
||||||
const [playerWhiteMeta, setPlayerWhiteMeta] = useState({});
|
|
||||||
const [showMenu, setShowMenu] = useState(false);
|
|
||||||
const playerState = state?.meta?.playerState;
|
|
||||||
const game = state.active?.game;
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchGameAPI = async () => {
|
|
||||||
const response = await gamesServices.getGameService(gameId);
|
|
||||||
if (response) {
|
|
||||||
const action = {
|
|
||||||
type: "GAMES",
|
|
||||||
message: "SET_ACTIVE",
|
|
||||||
body: response,
|
|
||||||
};
|
|
||||||
return dispatch(action);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
fetchGameAPI();
|
|
||||||
}, [gameId, dispatch]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const roomSocketConnect = () => {
|
|
||||||
const game = state.active.game;
|
|
||||||
const user = state.user;
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "CONNECT_GAME",
|
|
||||||
body: { game, user, dispatch },
|
|
||||||
};
|
|
||||||
return dispatch(action);
|
|
||||||
};
|
|
||||||
roomSocketConnect();
|
|
||||||
}, [state.active.game, dispatch, state.user]);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (!game || !playerState) return;
|
|
||||||
const { playerBlack, playerBlackRank, playerWhite, playerWhiteRank } = game;
|
|
||||||
const { bCaptures, wCaptures } = playerState;
|
|
||||||
setPlayerBlackMeta({
|
|
||||||
player: playerBlack,
|
|
||||||
rank: playerBlackRank,
|
|
||||||
captures: bCaptures,
|
|
||||||
stones: "black",
|
|
||||||
});
|
|
||||||
setPlayerWhiteMeta({
|
|
||||||
player: playerWhite,
|
|
||||||
rank: playerWhiteRank,
|
|
||||||
captures: wCaptures,
|
|
||||||
stones: "white",
|
|
||||||
});
|
|
||||||
}, [playerState, game]);
|
|
||||||
|
|
||||||
const handleResignClick = (player) => {
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "RESIGN",
|
|
||||||
body: { game, player },
|
|
||||||
};
|
|
||||||
dispatch(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handlePassClick = (player) => {
|
|
||||||
if (state?.meta && state?.meta?.winner) return;
|
|
||||||
if (state?.meta && state?.meta?.turn === 0) {
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "END_GAME",
|
|
||||||
body: { game, player },
|
|
||||||
};
|
|
||||||
return dispatch(action);
|
|
||||||
}
|
|
||||||
const action = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "PASS",
|
|
||||||
body: { game, player },
|
|
||||||
};
|
|
||||||
dispatch(action);
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="Game" data-testid="Game">
|
|
||||||
<Menu
|
|
||||||
showMenu={showMenu}
|
|
||||||
clickClose={() => setShowMenu(false)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
<div className="Game__meta-container">
|
|
||||||
<span className="Game__socket-flag">{state.socket ? "✓" : " ⃠"}</span>
|
|
||||||
<Logo />
|
|
||||||
{state?.meta?.winner ? (
|
|
||||||
<p>
|
|
||||||
{`winner: ${
|
|
||||||
state.meta.winner === 1
|
|
||||||
? playerBlackMeta?.player
|
|
||||||
: playerWhiteMeta?.player
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</p>
|
|
||||||
) : (
|
|
||||||
<></>
|
|
||||||
)}
|
|
||||||
<p>Timer</p>
|
|
||||||
<p>? Game Tree</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="Game__board-container">
|
|
||||||
<PlayerArea
|
|
||||||
handleResignClick={handleResignClick}
|
|
||||||
handlePassClick={handlePassClick}
|
|
||||||
playerMeta={
|
|
||||||
state.user &&
|
|
||||||
playerBlackMeta.playerBlack &&
|
|
||||||
state.user === playerBlackMeta.playerBlack
|
|
||||||
? playerBlackMeta
|
|
||||||
: playerWhiteMeta
|
|
||||||
}
|
|
||||||
turn={state?.meta?.turn}
|
|
||||||
/>
|
|
||||||
<Board
|
|
||||||
dispatch={dispatch}
|
|
||||||
game={state.active.game}
|
|
||||||
meta={state.meta}
|
|
||||||
user={state.user}
|
|
||||||
board={state.board}
|
|
||||||
/>
|
|
||||||
<PlayerArea
|
|
||||||
handleResignClick={handleResignClick}
|
|
||||||
handlePassClick={handlePassClick}
|
|
||||||
playerMeta={
|
|
||||||
state.user &&
|
|
||||||
playerBlackMeta.playerWhite &&
|
|
||||||
state.user === playerWhiteMeta.playerWhite
|
|
||||||
? playerWhiteMeta
|
|
||||||
: playerBlackMeta
|
|
||||||
}
|
|
||||||
Kifu={<Kifu clickKifu={() => setShowMenu(true)} />}
|
|
||||||
turn={state?.meta?.turn}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="Game__message-container">
|
|
||||||
<p>Messages</p>
|
|
||||||
<p>Message Form</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default Game;
|
|
|
@ -1,18 +0,0 @@
|
||||||
div.Game {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 2fr 1fr;
|
|
||||||
height: 100%;
|
|
||||||
width: 100%;
|
|
||||||
background: radial-gradient(
|
|
||||||
farthest-corner at 55% 40%,
|
|
||||||
rgb(150, 200, 220) 0%, rgb(97, 166, 194) 65%, rgb(70,100,120) 90%, rgb(40, 80, 90) 100%
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
div.Game__board-container {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
justify-content: space-evenly;
|
|
||||||
max-width: 50vw;
|
|
||||||
max-height: 100vh;
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom'
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { initState } from '../../reducers/init/reducer.init';
|
|
||||||
import Game from './Game';
|
|
||||||
|
|
||||||
const state = initState();
|
|
||||||
test('renders Game without crashing', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<Game state={state} dispatch={()=>{}}/>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const GameDiv = getByTestId('Game');
|
|
||||||
expect(GameDiv).toBeInTheDocument();
|
|
||||||
});
|
|
|
@ -1,54 +0,0 @@
|
||||||
import React, { useEffect, useState } from 'react';
|
|
||||||
import './Home.scss';
|
|
||||||
import roomsServices from '../../services/api/roomsServices';
|
|
||||||
import RoomButton from '../../components/Button/Room/Room';
|
|
||||||
|
|
||||||
import Loading from '../../components/Display/Loading/Loading';
|
|
||||||
|
|
||||||
const Home = props => {
|
|
||||||
const state = props.state || {};
|
|
||||||
const dispatch = props.dispatch;
|
|
||||||
const [ roomDetail, setRoomDetail ] = useState(1);
|
|
||||||
|
|
||||||
const showRoomDetail = id => roomDetail === id ? setRoomDetail(0) : setRoomDetail(id);
|
|
||||||
|
|
||||||
const renderRooms = () => {
|
|
||||||
const rooms = state.rooms || [];
|
|
||||||
if (rooms.length) {
|
|
||||||
return rooms.map(roomData => (
|
|
||||||
<RoomButton
|
|
||||||
key={`room-${roomData.id}`}
|
|
||||||
room={roomData}
|
|
||||||
roomDetail={roomDetail === roomData.id}
|
|
||||||
showRoomDetail={showRoomDetail}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
// TODO stub loader
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchRoomsAPI = async () => {
|
|
||||||
const response = await roomsServices.indexService();
|
|
||||||
if (response) {
|
|
||||||
const action = {
|
|
||||||
type: 'ROOMS',
|
|
||||||
message: 'SET_ROOMS',
|
|
||||||
body: response.rooms
|
|
||||||
}
|
|
||||||
return dispatch(action)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchRoomsAPI();
|
|
||||||
}, [ dispatch ])
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="Home" data-testid="Home">
|
|
||||||
{renderRooms()}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Home;
|
|
|
@ -1,11 +0,0 @@
|
||||||
@import '../../../public/stylesheets/partials/variables';
|
|
||||||
@import '../../../public/stylesheets/partials/mixins';
|
|
||||||
|
|
||||||
div.Home {
|
|
||||||
@include fullspan;
|
|
||||||
background-color: map-get($colors, "home");
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fill, 22vh);
|
|
||||||
grid-template-rows: repeat(auto-fill, minmax(22vh, 1fr));
|
|
||||||
overflow: scroll;
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Auth from '../../components/Form/Auth/Auth';
|
|
||||||
import NewRoomButton from '../../components/Button/NewRoom/NewRoom';
|
|
||||||
import FindRoomForm from '../../components/Form/FindRoom/FindRoom';
|
|
||||||
import LibraryButton from '../../components/Button/Library/Library';
|
|
||||||
|
|
||||||
const HomeSidebar = (props) => {
|
|
||||||
const { state, dispatch } = props;
|
|
||||||
|
|
||||||
const ifUser = <>
|
|
||||||
<FindRoomForm />
|
|
||||||
<NewRoomButton />
|
|
||||||
<LibraryButton />
|
|
||||||
</>
|
|
||||||
|
|
||||||
const ifNoUser = <Auth state={state} dispatch={dispatch} />
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav>
|
|
||||||
{
|
|
||||||
state.user.username
|
|
||||||
? ifUser
|
|
||||||
: ifNoUser
|
|
||||||
}
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default HomeSidebar;
|
|
|
@ -1,54 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './MainWrapper.scss';
|
|
||||||
|
|
||||||
import NavBar from '../NavBar/NavBar';
|
|
||||||
import Sidebar from '../Sidebar/Sidebar';
|
|
||||||
import Account from '../../Account/Account';
|
|
||||||
import Game from '../../Game/Game';
|
|
||||||
import Home from '../../Home/Home';
|
|
||||||
import News from '../../News/News';
|
|
||||||
import Room from '../../Room/Room';
|
|
||||||
|
|
||||||
const MainWrapper = (props) => {
|
|
||||||
const { state, page, dispatch } = props;
|
|
||||||
|
|
||||||
const setWrapperWithSidebarAndPage = () => {
|
|
||||||
if (page === 'game') return selectPage()
|
|
||||||
return (
|
|
||||||
<div className="main-wrapper" data-testid="main-wrapper">
|
|
||||||
<NavBar state={state}/>
|
|
||||||
<div className="content-wrapper">
|
|
||||||
<Sidebar page={page} state={state} dispatch={dispatch}/>
|
|
||||||
<main>
|
|
||||||
{selectPage()}
|
|
||||||
</main>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const selectPage = () =>{
|
|
||||||
switch (page) {
|
|
||||||
case 'account':
|
|
||||||
return <Account state={state} dispatch={dispatch}/>
|
|
||||||
case 'game':
|
|
||||||
return <Game state={state} dispatch={dispatch}/>
|
|
||||||
case 'home':
|
|
||||||
return <Home state={state} dispatch={dispatch}/>
|
|
||||||
case 'news':
|
|
||||||
return <News state={state} dispatch={dispatch}/>
|
|
||||||
case 'room':
|
|
||||||
return <Room state={state} dispatch={dispatch}/>
|
|
||||||
default:
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
{ setWrapperWithSidebarAndPage() }
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default MainWrapper;
|
|
|
@ -1,48 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/mixins';
|
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
div.main-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
@include fullspan;
|
|
||||||
|
|
||||||
div.NavBar {
|
|
||||||
max-height: 10vh;
|
|
||||||
min-height: 5vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.content-wrapper {
|
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
@include fullspan;
|
|
||||||
|
|
||||||
aside {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
flex-grow: 1;
|
|
||||||
max-width: 20vw;
|
|
||||||
a {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
display: block;
|
|
||||||
max-width: 80vw;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
|
||||||
color:map-get($colors, 'sidebar_link');
|
|
||||||
cursor: pointer;
|
|
||||||
font-family: inherit;
|
|
||||||
font-size: 110%;
|
|
||||||
font-weight: bold;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
padding: 0;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
import React from "react";
|
|
||||||
import { Link } from "react-router-dom";
|
|
||||||
import "./NavBar.scss";
|
|
||||||
import Logo from "../../../components/Display/Logo/Logo";
|
|
||||||
|
|
||||||
const NavBar = ({ state }) => {
|
|
||||||
return (
|
|
||||||
<div className="NavBar" data-testid="NavBar">
|
|
||||||
<Link to="/home">
|
|
||||||
<div className="NavBar__logo">
|
|
||||||
<Logo />
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/home">
|
|
||||||
<div className="NavBar__menu-item NavBar__home">
|
|
||||||
<p className="--link">Find a Game</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/news">
|
|
||||||
<div className="NavBar__menu-item NavBar__news">
|
|
||||||
<p className="--link">News</p>
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
|
|
||||||
<Link to="/account">
|
|
||||||
<div className="NavBar__menu-item NavBar__account">
|
|
||||||
{state.user.username ? state.user.username : <></>}
|
|
||||||
</div>
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
export default NavBar;
|
|
|
@ -1,22 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/_variables.scss';
|
|
||||||
|
|
||||||
div.NavBar {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row nowrap;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
background-color: map-get($colors, "nav_bar");
|
|
||||||
color: map-get($colors, "nav_link");
|
|
||||||
|
|
||||||
p {
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
a {
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a{
|
|
||||||
color: inherit;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import NavBar from './NavBar';
|
|
||||||
import { initState } from '../../../reducers/init/reducer.init';
|
|
||||||
|
|
||||||
test('renders NavBar without crashing', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<NavBar state={initState()}/>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const NavBarDiv = getByTestId('NavBar');
|
|
||||||
expect(NavBarDiv).toBeInTheDocument();
|
|
||||||
});
|
|
|
@ -1,33 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import './Sidebar.scss';
|
|
||||||
|
|
||||||
import AccountSidebar from '../../Account/AccountSidebar';
|
|
||||||
import HomeSidebar from '../../Home/HomeSidebar';
|
|
||||||
import NewsSidebar from '../../News/NewsSidebar';
|
|
||||||
import RoomSidebar from '../../Room/RoomSidebar';
|
|
||||||
|
|
||||||
const Sidebar = (props) => {
|
|
||||||
const {page, state, dispatch} = props
|
|
||||||
const displayComponent = () =>{
|
|
||||||
switch (page) {
|
|
||||||
case 'account':
|
|
||||||
return <AccountSidebar state={state} dispatch={dispatch}/>
|
|
||||||
case 'home':
|
|
||||||
return <HomeSidebar state={state} dispatch={dispatch}/>
|
|
||||||
case 'news':
|
|
||||||
return <NewsSidebar state={state} dispatch={dispatch}/>
|
|
||||||
case 'room':
|
|
||||||
return <RoomSidebar state={state} dispatch={dispatch}/>
|
|
||||||
default:
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<aside className="Sidebar" data-testid="Sidebar">
|
|
||||||
{displayComponent()}
|
|
||||||
</aside>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Sidebar;
|
|
|
@ -1,41 +0,0 @@
|
||||||
@import '../../../../public/stylesheets/partials/variables';
|
|
||||||
|
|
||||||
aside {
|
|
||||||
color: map-get($colors, "sidebar_link");
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
|
|
||||||
nav {
|
|
||||||
align-items: stretch;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
h3 {
|
|
||||||
font-size: 130%;
|
|
||||||
margin: .2em auto;
|
|
||||||
text-transform: capitalize;
|
|
||||||
}
|
|
||||||
|
|
||||||
input {
|
|
||||||
display: block;
|
|
||||||
margin: .2em 0 .2em auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.nav__section {
|
|
||||||
border: rgba(100,100,100,0.5) solid .25em;
|
|
||||||
margin: .5em;
|
|
||||||
padding: .75em .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
p.nav__section__label {
|
|
||||||
font-size: 110%;
|
|
||||||
margin-bottom: .5em;
|
|
||||||
text-decoration: none;
|
|
||||||
|
|
||||||
&:hover {
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,93 +0,0 @@
|
||||||
import React, { useEffect } from 'react';
|
|
||||||
import { useParams } from 'react-router-dom';
|
|
||||||
import './Room.scss';
|
|
||||||
import roomsServices from '../../services/api/roomsServices';
|
|
||||||
import GameButton from '../../components/Button/Game/Game';
|
|
||||||
import Message from '../../components/Display/Message/Message';
|
|
||||||
import ActionError from '../../components/Error/ActionError/ActionError';
|
|
||||||
|
|
||||||
import Development from '../../components/Display/Development/Development';
|
|
||||||
import Loading from '../../components/Display/Loading/Loading';
|
|
||||||
|
|
||||||
const Room = (props) => {
|
|
||||||
const { state, dispatch } = props;
|
|
||||||
const roomId = parseInt(useParams().id) || 0;
|
|
||||||
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const fetchRoomAPI = async () => {
|
|
||||||
const response = await roomsServices.getRoomService(roomId);
|
|
||||||
if (response) {
|
|
||||||
const action = {
|
|
||||||
type: 'ROOMS',
|
|
||||||
message: 'JOIN_ROOM',
|
|
||||||
body: response
|
|
||||||
}
|
|
||||||
return dispatch(action);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fetchRoomAPI();
|
|
||||||
}, [ roomId, dispatch ])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const roomSocketConnect = () => {
|
|
||||||
const action = {
|
|
||||||
type: 'SOCKET',
|
|
||||||
message: 'CONNECT_ROOM',
|
|
||||||
body: {user: state.user, room: roomId, dispatch}
|
|
||||||
}
|
|
||||||
dispatch(action)
|
|
||||||
}
|
|
||||||
roomSocketConnect();
|
|
||||||
}, [ roomId, state.user, dispatch ])
|
|
||||||
|
|
||||||
const renderGames = () => {
|
|
||||||
const games = state.games || [];
|
|
||||||
if (games.length) {
|
|
||||||
return games.map(gameData => (
|
|
||||||
<GameButton
|
|
||||||
key={`game-${gameData.id}`}
|
|
||||||
game={gameData}
|
|
||||||
dispatch={dispatch}
|
|
||||||
user={gameData.playerBlack === state.user.username || gameData.playerWhite === state.user.username}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
const renderMessages = () => {
|
|
||||||
const messages = state.messages || [];
|
|
||||||
if (messages.length) {
|
|
||||||
return messages.map((messageData, idx) => (
|
|
||||||
<Message
|
|
||||||
key={`message-${idx}`}
|
|
||||||
message={messageData}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
}
|
|
||||||
return <Loading />
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
return (
|
|
||||||
<div className="Room" data-testid="Room">
|
|
||||||
|
|
||||||
<div className="Room__heading">
|
|
||||||
{state.errors.joinGame ? <ActionError error={state.errors.joinGame}/> : <></>}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="Room__game-container">
|
|
||||||
{renderGames()}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="Room__message-container">
|
|
||||||
{renderMessages()}
|
|
||||||
<Development />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default Room;
|
|
|
@ -1,42 +0,0 @@
|
||||||
@import '../../../public/stylesheets/partials/_variables';
|
|
||||||
|
|
||||||
div.Room {
|
|
||||||
align-items: initial;
|
|
||||||
background: map-get($backgrounds, "game_room");
|
|
||||||
display: flex;
|
|
||||||
flex-flow: column nowrap;
|
|
||||||
height: 100%;
|
|
||||||
justify-content: stretch;
|
|
||||||
overflow: scroll;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
|
|
||||||
div.Room__heading {
|
|
||||||
align-self: center;
|
|
||||||
height: 100%;
|
|
||||||
position: sticky;
|
|
||||||
top: 1em;
|
|
||||||
z-index: 3;
|
|
||||||
|
|
||||||
span.error--action {
|
|
||||||
padding: 1em;
|
|
||||||
background-color: map-get($colors, "error");
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
div.Room__game-container {
|
|
||||||
min-height: 100%;
|
|
||||||
min-width: 100%;
|
|
||||||
justify-self: flex-start;
|
|
||||||
align-self: flex-start;
|
|
||||||
display: flex;
|
|
||||||
flex-flow: row wrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
div.Room__message-container {
|
|
||||||
height: 100%;
|
|
||||||
justify-self: flex-end;
|
|
||||||
align-self: flex-end;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,16 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
import { render } from '@testing-library/react';
|
|
||||||
import { BrowserRouter as Router } from 'react-router-dom';
|
|
||||||
import { initState } from '../../reducers/init/reducer.init';
|
|
||||||
import Room from './Room';
|
|
||||||
|
|
||||||
const state = initState();
|
|
||||||
test('renders Room without crashing', () => {
|
|
||||||
const { getByTestId } = render(
|
|
||||||
<Router>
|
|
||||||
<Room state={state} dispatch={() => {}}/>
|
|
||||||
</Router>
|
|
||||||
);
|
|
||||||
const RoomDiv = getByTestId('Room');
|
|
||||||
expect(RoomDiv).toBeInTheDocument();
|
|
||||||
});
|
|
|
@ -1,31 +0,0 @@
|
||||||
import React from 'react';
|
|
||||||
|
|
||||||
import Auth from '../../components/Form/Auth/Auth';
|
|
||||||
import FindGameForm from '../../components/Form/FindGame/FindGame';
|
|
||||||
import NewGameButton from '../../components/Button/NewGame/NewGame';
|
|
||||||
import RoomArchiveButton from '../../components/Button/RoomArchive/RoomArchive';
|
|
||||||
|
|
||||||
const RoomSidebar = props => {
|
|
||||||
const {state, dispatch} = props;
|
|
||||||
|
|
||||||
const showAuth = () => {
|
|
||||||
if (state.errors.joinGame) {
|
|
||||||
return (
|
|
||||||
<Auth state={state} dispatch={dispatch} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return <></>
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<nav>
|
|
||||||
<h3>{ state.currentRoom.name }</h3>
|
|
||||||
{showAuth()}
|
|
||||||
<FindGameForm />
|
|
||||||
<NewGameButton />
|
|
||||||
<RoomArchiveButton />
|
|
||||||
</nav>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default RoomSidebar;
|
|
|
@ -1,23 +0,0 @@
|
||||||
export const authReducer = (state, action) => {
|
|
||||||
switch (action.message) {
|
|
||||||
case "LOGIN":
|
|
||||||
return loginReducer(state, action);
|
|
||||||
|
|
||||||
case "SIGNUP":
|
|
||||||
return loginReducer(state, action);
|
|
||||||
|
|
||||||
case "GUEST":
|
|
||||||
return loginReducer(state, action);
|
|
||||||
|
|
||||||
case "LOGOUT":
|
|
||||||
return state;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function loginReducer(state, action) {
|
|
||||||
const newUser = action.body;
|
|
||||||
return { ...state, user: newUser };
|
|
||||||
}
|
|
|
@ -1,30 +0,0 @@
|
||||||
export const errorReducer = (state, action) => {
|
|
||||||
switch (action.message) {
|
|
||||||
case 'AUTH_ERROR':
|
|
||||||
return authErrorReducer(state, action);
|
|
||||||
|
|
||||||
case 'JOIN_ROOM_ERROR':
|
|
||||||
return joinRoomErrorReducer(state, action);
|
|
||||||
|
|
||||||
case 'JOIN_GAME_ERROR':
|
|
||||||
return joinGameErrorReducer(state, action);
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function authErrorReducer(state, action) {
|
|
||||||
const auth = action.body.authError;
|
|
||||||
return {...state, errors: {auth} };
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinRoomErrorReducer(state, action) {
|
|
||||||
const joinRoom = action.body.joinRoomError;
|
|
||||||
return { ...state, errors: {joinRoom} }
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinGameErrorReducer(state, action) {
|
|
||||||
const joinGame = action.body.joinGameError;
|
|
||||||
return { ...state, errors: {joinGame} }
|
|
||||||
}
|
|
|
@ -1,104 +0,0 @@
|
||||||
import { stateReducer } from "../reducer";
|
|
||||||
|
|
||||||
export const gamesReducer = (state, action) => {
|
|
||||||
switch (action.message) {
|
|
||||||
case "SET_GAMES": {
|
|
||||||
const games = formatGames(action);
|
|
||||||
return { ...state, games };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "JOIN_REQUEST": {
|
|
||||||
return joinRequest(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "UPDATE_BOARD": {
|
|
||||||
return updateBoard(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "GAME_RESIGN": {
|
|
||||||
return gameResign(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "SET_ACTIVE": {
|
|
||||||
return { ...state, active: action.body };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "GAME_END": {
|
|
||||||
return gameEnd(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
default: {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// parse ranks from db in K9 format to 9k format
|
|
||||||
function parseRank(rank) {
|
|
||||||
switch (rank[0]) {
|
|
||||||
// Dan ranks
|
|
||||||
case "D":
|
|
||||||
return `${rank.slice(1)}${rank[0].toLowerCase()}`;
|
|
||||||
// Kyu ranks
|
|
||||||
case "K":
|
|
||||||
return `${rank.slice(1)}${rank[0].toLowerCase()}`;
|
|
||||||
// Unranked
|
|
||||||
case "U":
|
|
||||||
return "?";
|
|
||||||
default:
|
|
||||||
return "?";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatGames(action) {
|
|
||||||
const games = [...action.body].map((game) => {
|
|
||||||
if (game.playerBlackRank) {
|
|
||||||
game.playerBlackRank = parseRank(game.playerBlackRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (game.playerWhiteRank) {
|
|
||||||
game.playerWhiteRank = parseRank(game.playerWhiteRank);
|
|
||||||
}
|
|
||||||
|
|
||||||
return game;
|
|
||||||
});
|
|
||||||
|
|
||||||
return games;
|
|
||||||
}
|
|
||||||
|
|
||||||
function joinRequest(state, action) {
|
|
||||||
if (!Object.entries(state.user).length) {
|
|
||||||
const errAction = {
|
|
||||||
type: "ERR",
|
|
||||||
message: "JOIN_GAME_ERROR",
|
|
||||||
body: { joinGameError: "user not logged in" },
|
|
||||||
};
|
|
||||||
return stateReducer(state, errAction);
|
|
||||||
}
|
|
||||||
const id = action.body;
|
|
||||||
return { ...state, joinGame: id };
|
|
||||||
}
|
|
||||||
|
|
||||||
function updateBoard(state, action) {
|
|
||||||
console.log(action.body);
|
|
||||||
const { gameRecord, pass, turn, winner, playerState } = action.body.meta;
|
|
||||||
const territory = action.body.territory;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
board: action.body.board,
|
|
||||||
meta: { gameRecord, pass, turn, winner, playerState, territory },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function gameResign(state, action) {
|
|
||||||
const { gameRecord, pass, turn, winner, playerState } = action.body;
|
|
||||||
return {
|
|
||||||
...state,
|
|
||||||
meta: { gameRecord, pass, turn, winner, playerState },
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
function gameEnd(state, action) {
|
|
||||||
const { winner, score } = action.body.meta;
|
|
||||||
return { ...state, meta: { ...state.meta, winner, score } };
|
|
||||||
}
|
|
|
@ -1,60 +0,0 @@
|
||||||
import {stateReducer} from '../reducer';
|
|
||||||
import { initState } from '../init/reducer.init';
|
|
||||||
|
|
||||||
const gamesData = [
|
|
||||||
{
|
|
||||||
komi:6.5, handicap:0, board_size:19,
|
|
||||||
player_black:"anon", player_white:"anon",
|
|
||||||
player_black_rank:"K3", player_white_rank:"K2"
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const activeGameData = {
|
|
||||||
game: {
|
|
||||||
id: 1,
|
|
||||||
application: "node-go",
|
|
||||||
application_version: "0.1.0",
|
|
||||||
board_size: 19,
|
|
||||||
komi: 6.5,
|
|
||||||
handicap: 0,
|
|
||||||
open: false,
|
|
||||||
win_type: null,
|
|
||||||
player_black: "user-one",
|
|
||||||
player_black_rank: "UR",
|
|
||||||
player_white: "user-two",
|
|
||||||
player_white_rank: "UR",
|
|
||||||
black_captures: null,
|
|
||||||
white_captures: null,
|
|
||||||
score: null,
|
|
||||||
description: null,
|
|
||||||
event: null,
|
|
||||||
round: null,
|
|
||||||
name: null,
|
|
||||||
room: 1,
|
|
||||||
main_time: "untimed",
|
|
||||||
time_period: 1,
|
|
||||||
period_length: 0,
|
|
||||||
overtime: "untimed",
|
|
||||||
overtime_period: 0,
|
|
||||||
overtime_length: 0
|
|
||||||
},
|
|
||||||
record: []
|
|
||||||
}
|
|
||||||
|
|
||||||
it('default returns state unaltered', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'GAMES', message: '', body: gamesData};
|
|
||||||
expect(stateReducer(state, action)).toEqual(state);
|
|
||||||
})
|
|
||||||
|
|
||||||
it('set games returns state with games', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'GAMES', message: 'SET_GAMES', body: gamesData};
|
|
||||||
expect(stateReducer(state, action)).toEqual({...state, games: gamesData});
|
|
||||||
})
|
|
||||||
|
|
||||||
it('active game returns state with active game details', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'GAMES', message: 'SET_ACTIVE', body: activeGameData};
|
|
||||||
expect(stateReducer(state, action)).toEqual({...state, active: activeGameData});
|
|
||||||
})
|
|
|
@ -1,11 +0,0 @@
|
||||||
export const indexReducer = (state, action) => {
|
|
||||||
switch(action.message) {
|
|
||||||
|
|
||||||
case 'SET_USER':
|
|
||||||
const user = action.body;
|
|
||||||
return {...state, user};
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
export const initState = () => {
|
|
||||||
return {
|
|
||||||
active: {
|
|
||||||
game: {
|
|
||||||
id: 0, room: 0,
|
|
||||||
mainTime: '', timePeriod: 0, periodLength: 0, overtime: '', overtimePeriod: 0, overtimeLength: 0,
|
|
||||||
application: '', applicationVersion: '', open: false, description: null,
|
|
||||||
event: null, round: null, name: null,
|
|
||||||
winType: null, capturesBlack: null, capturesWhite: null, score: null,
|
|
||||||
boardSize: 0, komi: 0.0, handicap: 0,
|
|
||||||
playerBlack: '', playerBlackRank: '', playerWhite: '', playerWhiteRank: '',
|
|
||||||
},
|
|
||||||
record: []
|
|
||||||
},
|
|
||||||
|
|
||||||
board: {},
|
|
||||||
|
|
||||||
connect: { location: '', type: '' },
|
|
||||||
|
|
||||||
currentRoom: {
|
|
||||||
description: '', id: 0, language: '', name: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
errors: {},
|
|
||||||
|
|
||||||
games: [ {
|
|
||||||
boardSize: 0, handicap: 0, id: 0, komi: 0.0, open: false,
|
|
||||||
playerBlack: '', playerBlackRank: '', playerWhite: '',
|
|
||||||
playerWhiteRank: '', winType: null
|
|
||||||
} ],
|
|
||||||
|
|
||||||
joinGame: {},
|
|
||||||
|
|
||||||
messages: [ {
|
|
||||||
admin: false, content: '', username: ''
|
|
||||||
} ],
|
|
||||||
|
|
||||||
socket: {
|
|
||||||
connected: false,
|
|
||||||
nsp: ''
|
|
||||||
},
|
|
||||||
|
|
||||||
user: { username: '', email: '', id: 0 }
|
|
||||||
};
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
export const messagesReducer = (state, action) => {
|
|
||||||
switch(action.message) {
|
|
||||||
|
|
||||||
case 'SET_MESSAGES':
|
|
||||||
const messages = action.body;
|
|
||||||
return {...state, messages};
|
|
||||||
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,20 +0,0 @@
|
||||||
import {stateReducer} from '../reducer';
|
|
||||||
import { initState } from '../init/reducer.init';
|
|
||||||
|
|
||||||
const messagesData = [
|
|
||||||
{
|
|
||||||
"content": "Hey! Welcome to the general room!", "username": "userOne", "admin": true
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
it('default returns state unaltered', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'MESSAGES', message: '', body: messagesData};
|
|
||||||
expect(stateReducer(state, action)).toEqual(state);
|
|
||||||
})
|
|
||||||
|
|
||||||
it('set messages returns state with messages', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'MESSAGES', message: 'SET_MESSAGES', body: messagesData};
|
|
||||||
expect(stateReducer(state, action)).toEqual({...state, messages: messagesData});
|
|
||||||
})
|
|
|
@ -1,44 +0,0 @@
|
||||||
import { initState } from './init/reducer.init';
|
|
||||||
import { authReducer } from './auth/reducer.auth';
|
|
||||||
import { errorReducer } from './err/reducer.err';
|
|
||||||
import { indexReducer } from './index/reducer.index';
|
|
||||||
import { roomsReducer } from './rooms/reducer.rooms';
|
|
||||||
import { messagesReducer } from './messages/reducer.messages';
|
|
||||||
import { gamesReducer } from './games/reducer.games';
|
|
||||||
import { socketReducer } from './socket/reducer.socket';
|
|
||||||
|
|
||||||
|
|
||||||
export const stateReducer = (state, action) => {
|
|
||||||
const errorStrippedState = stripErrors({...state});
|
|
||||||
|
|
||||||
switch (action.type) {
|
|
||||||
case 'INIT': return initState();
|
|
||||||
|
|
||||||
case 'AUTH':
|
|
||||||
return authReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'GAMES':
|
|
||||||
return gamesReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'INDEX':
|
|
||||||
return indexReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'MESSAGES':
|
|
||||||
return messagesReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'ROOMS':
|
|
||||||
return roomsReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'SOCKET':
|
|
||||||
return socketReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
case 'ERR':
|
|
||||||
return errorReducer(errorStrippedState, action);
|
|
||||||
|
|
||||||
default: return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function stripErrors(state) {
|
|
||||||
return {...state, errors: {}}
|
|
||||||
}
|
|
|
@ -1,80 +0,0 @@
|
||||||
import { stateReducer } from '../reducer';
|
|
||||||
|
|
||||||
export const roomsReducer = (state, action) => {
|
|
||||||
switch(action.message) {
|
|
||||||
|
|
||||||
case 'SET_ROOMS':
|
|
||||||
const rooms = action.body;
|
|
||||||
return {...state, rooms};
|
|
||||||
|
|
||||||
case 'SET_CURRENT':
|
|
||||||
const currentRoom = action.body;
|
|
||||||
return {...state, currentRoom};
|
|
||||||
|
|
||||||
case 'JOIN_ROOM': {
|
|
||||||
const stateWithRoom = setCurrentRoom(state, action);
|
|
||||||
const stateWithMessages = setMessages(stateWithRoom, action);
|
|
||||||
if (!action.body.roomGames) {
|
|
||||||
return setJoinRoomError(state);
|
|
||||||
}
|
|
||||||
const stateWithGames = setGames(stateWithMessages, action);
|
|
||||||
|
|
||||||
return stateWithGames;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'NEW_USER': {
|
|
||||||
if (!action.data) {
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function setMessages(state, action) {
|
|
||||||
if(action.body.messages.length) {
|
|
||||||
const messages = action.body.messages;
|
|
||||||
const messageAction = {
|
|
||||||
type: 'MESSAGES',
|
|
||||||
message: 'SET_MESSAGES',
|
|
||||||
body: messages
|
|
||||||
}
|
|
||||||
return stateReducer(state, messageAction);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function setJoinRoomError(state, body) {
|
|
||||||
const errorAction = {
|
|
||||||
type: 'ERR',
|
|
||||||
message: 'JOIN_ROOM_ERROR',
|
|
||||||
body: { joinRoomError: 'Game room has no games' }
|
|
||||||
}
|
|
||||||
return stateReducer(state, errorAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setCurrentRoom(state, action) {
|
|
||||||
const currentRoom = action.body.currentRoom;
|
|
||||||
const roomAction = {
|
|
||||||
type: 'ROOMS',
|
|
||||||
message: 'SET_CURRENT',
|
|
||||||
body: currentRoom
|
|
||||||
}
|
|
||||||
return stateReducer(state, roomAction);
|
|
||||||
}
|
|
||||||
|
|
||||||
function setGames(state, action) {
|
|
||||||
if (action.body.roomGames.length) {
|
|
||||||
const games = action.body.roomGames;
|
|
||||||
const gamesAction = {
|
|
||||||
type: 'GAMES',
|
|
||||||
message: 'SET_GAMES',
|
|
||||||
body: games
|
|
||||||
}
|
|
||||||
return stateReducer(state, gamesAction);
|
|
||||||
}
|
|
||||||
return state;
|
|
||||||
}
|
|
|
@ -1,53 +0,0 @@
|
||||||
import {stateReducer} from '../reducer';
|
|
||||||
import { initState } from '../init/reducer.init';
|
|
||||||
|
|
||||||
const roomsData = [
|
|
||||||
{
|
|
||||||
description: "A general place to play Go",
|
|
||||||
id: 1,
|
|
||||||
language: "EN",
|
|
||||||
name: "main"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const joinRoomData = {
|
|
||||||
currentRoom: {
|
|
||||||
id:1, name:"main",
|
|
||||||
description:"A general place to play Go", language:"EN"
|
|
||||||
},
|
|
||||||
roomGames: [
|
|
||||||
{
|
|
||||||
komi:6.5, handicap:0, board_size:19,
|
|
||||||
player_black:"anon", player_white:"anon",
|
|
||||||
player_black_rank:"K3", player_white_rank:"K2"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
messages: [
|
|
||||||
{
|
|
||||||
content: "Hey! Welcome to the general room!", username: "userOne", admin: true
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
it('default returns state unaltered', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'ROOMS', message: '', body: JSON.stringify(roomsData)};
|
|
||||||
expect(stateReducer(state, action)).toEqual(state);
|
|
||||||
})
|
|
||||||
|
|
||||||
it('set rooms returns state with rooms added', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'ROOMS', message: 'SET_ROOMS', body: roomsData};
|
|
||||||
expect(stateReducer(state, action)).toEqual({...state, rooms: roomsData});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('join room returns state with current room, games and messages all populated', () => {
|
|
||||||
const state = initState();
|
|
||||||
const action = {type: 'ROOMS', message: 'JOIN_ROOM', body: joinRoomData};
|
|
||||||
expect(stateReducer(state, action)).toEqual({
|
|
||||||
...state,
|
|
||||||
currentRoom: joinRoomData.currentRoom,
|
|
||||||
messages: joinRoomData.messages,
|
|
||||||
games: joinRoomData.roomGames
|
|
||||||
})
|
|
||||||
});
|
|
|
@ -1,105 +0,0 @@
|
||||||
import { stateReducer } from "../reducer";
|
|
||||||
const io = require("../../io");
|
|
||||||
|
|
||||||
export const socketReducer = (state, action) => {
|
|
||||||
switch (action.message) {
|
|
||||||
case "CONNECTED":
|
|
||||||
return { ...state, connect: { type: "home", location: action.body.nsp } };
|
|
||||||
|
|
||||||
case "LAUNCH": {
|
|
||||||
const { nsp, dispatch } = action.body;
|
|
||||||
const launchedSocket = io.launch(nsp, dispatch);
|
|
||||||
return { ...state, socket: launchedSocket };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "CONNECT_ROOM": {
|
|
||||||
const { user, room, dispatch } = action.body;
|
|
||||||
let priorSocket = state.socket;
|
|
||||||
if (!priorSocket.nsp) {
|
|
||||||
priorSocket = io.launch("", dispatch);
|
|
||||||
}
|
|
||||||
if (priorSocket.nsp !== `/${room}`) {
|
|
||||||
priorSocket.emit("connect_room", { user, room });
|
|
||||||
priorSocket.close();
|
|
||||||
}
|
|
||||||
const socket = io.launch(room, dispatch);
|
|
||||||
return { ...state, socket };
|
|
||||||
}
|
|
||||||
|
|
||||||
case "CONNECT_GAME": {
|
|
||||||
return connectGame(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "MAKE_MOVE": {
|
|
||||||
return makeMove(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "RESIGN": {
|
|
||||||
return resign(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "PASS": {
|
|
||||||
return pass(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "TOGGLE_TERRITORY": {
|
|
||||||
return toggleTerritory(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
case "END_GAME": {
|
|
||||||
return endGame(state, action);
|
|
||||||
}
|
|
||||||
|
|
||||||
default:
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
function connectGame(state, action) {
|
|
||||||
const { user, game, dispatch } = action.body;
|
|
||||||
const priorSocket = state.socket;
|
|
||||||
let updatedState;
|
|
||||||
if (!priorSocket.nsp || priorSocket.nsp !== `/${game.room}`) {
|
|
||||||
const connectRoomAction = {
|
|
||||||
type: "SOCKET",
|
|
||||||
message: "CONNECT_ROOM",
|
|
||||||
body: { user, room: game.room, dispatch },
|
|
||||||
};
|
|
||||||
updatedState = stateReducer(state, connectRoomAction);
|
|
||||||
}
|
|
||||||
if (!updatedState) updatedState = { ...state };
|
|
||||||
const socket = updatedState.socket;
|
|
||||||
socket.emit("connect_game", { user, game });
|
|
||||||
return { ...updatedState };
|
|
||||||
}
|
|
||||||
|
|
||||||
function makeMove(state, action) {
|
|
||||||
const socket = state.socket;
|
|
||||||
socket.emit("make_move", { ...action.body });
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function resign(state, action) {
|
|
||||||
const socket = state.socket;
|
|
||||||
socket.emit("resign", { ...action.body });
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function pass(state, action) {
|
|
||||||
const socket = state.socket;
|
|
||||||
socket.emit("pass", { ...action.body });
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function toggleTerritory(state, action) {
|
|
||||||
const socket = state.socket;
|
|
||||||
socket.emit("toggle_territory", { ...action.body });
|
|
||||||
return state;
|
|
||||||
}
|
|
||||||
|
|
||||||
function endGame(state, action) {
|
|
||||||
console.log("end game");
|
|
||||||
const socket = state.socket;
|
|
||||||
socket.emit("end_game", { ...action.body });
|
|
||||||
return state;
|
|
||||||
}
|
|
|
@ -1,43 +0,0 @@
|
||||||
import config from '../../config';
|
|
||||||
|
|
||||||
const apiAddress = config.apiAddress;
|
|
||||||
const gamesAddress = `${apiAddress}/games`
|
|
||||||
|
|
||||||
var headers = new Headers();
|
|
||||||
headers.append('Content-Type', 'application/json');
|
|
||||||
headers.append('Accept', 'application/json');
|
|
||||||
headers.append('Sec-Fetch-Site', 'cross-site')
|
|
||||||
|
|
||||||
const getGameService = async (gameIndex) => {
|
|
||||||
const response = await fetch(`${gamesAddress}/${gameIndex}`,
|
|
||||||
{method: 'GET', credentials: 'include', headers: headers}
|
|
||||||
)
|
|
||||||
.then(res => res.text())
|
|
||||||
.then(text => JSON.parse(text))
|
|
||||||
.then(obj => {
|
|
||||||
// Reformat from SQL coluumn name convention to JS
|
|
||||||
delete Object.assign(obj.game, {applicationVersion: obj.game.application_version }).application_version;
|
|
||||||
delete Object.assign(obj.game, {boardSize: obj.game.board_size }).board_size;
|
|
||||||
delete Object.assign(obj.game, {playerBlack: obj.game.player_black }).player_black;
|
|
||||||
delete Object.assign(obj.game, {playerBlackRank: obj.game.player_black_rank }).player_black_rank;
|
|
||||||
delete Object.assign(obj.game, {playerWhite: obj.game.player_white }).player_white;
|
|
||||||
delete Object.assign(obj.game, {playerWhiteRank: obj.game.player_white_rank }).player_white_rank;
|
|
||||||
delete Object.assign(obj.game, {capturesWhite: obj.game.captures_white }).captures_white;
|
|
||||||
delete Object.assign(obj.game, {capturesBlack: obj.game.captures_black }).captures_black;
|
|
||||||
delete Object.assign(obj.game, {mainTime: obj.game.main_time }).main_time;
|
|
||||||
delete Object.assign(obj.game, {timePeriod: obj.game.time_period }).time_period;
|
|
||||||
delete Object.assign(obj.game, {periodLength: obj.game.period_length }).period_length;
|
|
||||||
delete Object.assign(obj.game, {overtimePeriod: obj.game.overtime_period }).overtime_period;
|
|
||||||
delete Object.assign(obj.game, {overtimeLength: obj.game.overtime_length }).overtime_length;
|
|
||||||
delete Object.assign(obj.game, {winType: obj.game.win_type }).win_type;
|
|
||||||
|
|
||||||
return obj;
|
|
||||||
})
|
|
||||||
.catch(err => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
getGameService
|
|
||||||
}
|
|
|
@ -1,50 +0,0 @@
|
||||||
import config from '../../config';
|
|
||||||
|
|
||||||
const apiAddress = config.apiAddress;
|
|
||||||
const roomsAddress = `${apiAddress}/rooms`
|
|
||||||
|
|
||||||
var headers = new Headers();
|
|
||||||
headers.append('Content-Type', 'application/json');
|
|
||||||
headers.append('Accept', 'application/json');
|
|
||||||
headers.append('Sec-Fetch-Site', 'cross-site')
|
|
||||||
|
|
||||||
const indexService = async () => {
|
|
||||||
const response = await fetch(roomsAddress,
|
|
||||||
{method: 'GET', credentials: 'include', headers: headers}
|
|
||||||
)
|
|
||||||
.then(res => res.text())
|
|
||||||
.then(text => JSON.parse(text))
|
|
||||||
.catch(err => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
const getRoomService = async (roomIndex) => {
|
|
||||||
const response = await fetch(`${roomsAddress}/${roomIndex}`,
|
|
||||||
{method: 'GET', credentials: 'include', headers: headers}
|
|
||||||
)
|
|
||||||
.then(res => res.text())
|
|
||||||
.then(text => JSON.parse(text))
|
|
||||||
|
|
||||||
.then(obj => {
|
|
||||||
obj.games = obj.roomGames.map(game => {
|
|
||||||
delete Object.assign(game, {boardSize: game.board_size }).board_size;
|
|
||||||
delete Object.assign(game, {playerBlack: game.player_black }).player_black;
|
|
||||||
delete Object.assign(game, {playerBlackRank: game.player_black_rank }).player_black_rank;
|
|
||||||
delete Object.assign(game, {playerWhite: game.player_white }).player_white;
|
|
||||||
delete Object.assign(game, {playerWhiteRank: game.player_white_rank }).player_white_rank;
|
|
||||||
delete Object.assign(game, {winType: game.win_type }).win_type;
|
|
||||||
|
|
||||||
return game;
|
|
||||||
})
|
|
||||||
return obj;
|
|
||||||
})
|
|
||||||
.catch(err => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default {
|
|
||||||
indexService,
|
|
||||||
getRoomService
|
|
||||||
}
|
|
|
@ -1,58 +0,0 @@
|
||||||
import config from "../config";
|
|
||||||
|
|
||||||
const authEndpoint = config.authAddress;
|
|
||||||
const signupEndpoint = `${authEndpoint}/signup`;
|
|
||||||
const loginEndpoint = `${authEndpoint}/login`;
|
|
||||||
const guestEndpoint = `${authEndpoint}/guest`;
|
|
||||||
|
|
||||||
var headers = new Headers();
|
|
||||||
headers.append("Content-Type", "application/json");
|
|
||||||
headers.append("Accept", "application/json");
|
|
||||||
headers.append("Sec-Fetch-Site", "cross-site");
|
|
||||||
|
|
||||||
const loginService = async (formData) => {
|
|
||||||
const response = await fetch(loginEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify(formData),
|
|
||||||
headers: headers,
|
|
||||||
})
|
|
||||||
.then((res) => res.text())
|
|
||||||
.then((text) => JSON.parse(text))
|
|
||||||
.catch((err) => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const signupService = async (formData) => {
|
|
||||||
const response = await fetch(signupEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
body: JSON.stringify(formData),
|
|
||||||
headers: headers,
|
|
||||||
})
|
|
||||||
.then((res) => res.text())
|
|
||||||
.then((text) => JSON.parse(text))
|
|
||||||
.catch((err) => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
const guestService = async () => {
|
|
||||||
const response = await fetch(guestEndpoint, {
|
|
||||||
method: "POST",
|
|
||||||
credentials: "include",
|
|
||||||
headers,
|
|
||||||
})
|
|
||||||
.then((res) => res.text())
|
|
||||||
.then((text) => JSON.parse(text))
|
|
||||||
.catch((err) => err);
|
|
||||||
|
|
||||||
return response;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default {
|
|
||||||
loginService,
|
|
||||||
signupService,
|
|
||||||
guestService,
|
|
||||||
};
|
|
|
@ -1,18 +0,0 @@
|
||||||
# Node Go
|
|
||||||
A browser application to play Go in real time.
|
|
||||||
|
|
||||||
## Development Demo
|
|
||||||
[The project in it's current state](https://play-node-go.herokuapp.com/)
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- [ ] Realtime communications
|
|
||||||
- [ ] Multiple game settings
|
|
||||||
- [ ] Customizable board size
|
|
||||||
|
|
||||||
|
|
||||||
## Built with
|
|
||||||
- [Express](https://expressjs.com)
|
|
||||||
- [React](https://reactjs.org)
|
|
||||||
- [PostgreSQL](https://postgresql.org)
|
|
||||||
- [Socket.io](https://socket.io)
|
|
||||||
- [Sass](https://sass-lang.com)
|
|
|
@ -1,24 +0,0 @@
|
||||||
const roomQueries = require('../../data/queries/room');
|
|
||||||
const messageQueries = require('../../data/queries/message');
|
|
||||||
const gameQueries = require('../../data/queries/game');
|
|
||||||
const moveQueries = require('../../data/queries/move');
|
|
||||||
|
|
||||||
const show = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const gameId = req.params.id;
|
|
||||||
if (!gameId) throw('missing game parameter')
|
|
||||||
|
|
||||||
// TODO Promise.all()
|
|
||||||
const game = await gameQueries.findGameById(gameId);
|
|
||||||
// const record = await moveQueries.findGameRecord(gameId);
|
|
||||||
// console.log(record)
|
|
||||||
res.status(200).json({game})
|
|
||||||
}
|
|
||||||
catch (err) {
|
|
||||||
res.status(500).json(err);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
show
|
|
||||||
}
|
|
|
@ -1,41 +0,0 @@
|
||||||
const roomQueries = require('../../data/queries/room');
|
|
||||||
const messageQueries = require('../../data/queries/message');
|
|
||||||
const gameQueries = require('../../data/queries/game');
|
|
||||||
const socket = require('../../socket');
|
|
||||||
|
|
||||||
const getAll = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const publicRooms = await roomQueries.findPublicRooms();
|
|
||||||
|
|
||||||
res.status(200).json({rooms: [...publicRooms]})
|
|
||||||
}
|
|
||||||
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
res.status(500).json(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const show = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
const roomId = req.params.id;
|
|
||||||
if (!roomId) throw('missing room parameter')
|
|
||||||
|
|
||||||
// TODO eventually add check for user's private rooms
|
|
||||||
|
|
||||||
const currentRoom = await roomQueries.findRoomById(roomId);
|
|
||||||
const messages = await messageQueries.findMessageByRoom(roomId);
|
|
||||||
const roomGames = await gameQueries.findGameByRoom(roomId);
|
|
||||||
const body = {currentRoom, messages, roomGames};
|
|
||||||
res.status(200).json(body);
|
|
||||||
}
|
|
||||||
catch (e) {
|
|
||||||
console.log(e)
|
|
||||||
res.status(500).json(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
getAll,
|
|
||||||
show
|
|
||||||
}
|
|
|
@ -1,88 +0,0 @@
|
||||||
const { validationResult } = require("express-validator");
|
|
||||||
|
|
||||||
const userQueries = require("../data/queries/user");
|
|
||||||
const { hashPassword, compareHash } = require("../services/bcrypt");
|
|
||||||
const signToken = require("../services/signToken");
|
|
||||||
const guestServices = require("../services/guestServices");
|
|
||||||
|
|
||||||
const checkValidationErrors = (req, res) => {
|
|
||||||
const errors = validationResult(req);
|
|
||||||
if (!errors.isEmpty()) {
|
|
||||||
return res.status(422).json({ errors: errors.array() });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const signup = async (req, res, next) => {
|
|
||||||
checkValidationErrors(req, res);
|
|
||||||
const user = req.body;
|
|
||||||
try {
|
|
||||||
delete user.confirmPassword;
|
|
||||||
const existingUser = await userQueries.findUserByNameOrEmail(user);
|
|
||||||
const hashedPassword = await hashPassword(user.password);
|
|
||||||
const secureUser = { ...user, password: hashedPassword };
|
|
||||||
if (existingUser.length) {
|
|
||||||
return res
|
|
||||||
.status(409)
|
|
||||||
.json({ errors: [{ auth: "User already exists!" }] });
|
|
||||||
}
|
|
||||||
|
|
||||||
const newUser = await userQueries.insertUser(secureUser);
|
|
||||||
signToken(res, newUser);
|
|
||||||
res.status(201).json({ ...newUser });
|
|
||||||
} catch (err) {
|
|
||||||
res.status(500).json(err);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const login = async (req, res, next) => {
|
|
||||||
checkValidationErrors(req, res);
|
|
||||||
const user = req.body;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const queryResults = await userQueries.findUserByNameOrEmail(user);
|
|
||||||
const savedUser = queryResults[0] || null;
|
|
||||||
|
|
||||||
if (!savedUser) {
|
|
||||||
return res.status(401).send({ errors: "bad credentials" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const hashedPassword = savedUser.password;
|
|
||||||
const passwordMatch = await compareHash(user.password, hashedPassword);
|
|
||||||
|
|
||||||
if (!passwordMatch) {
|
|
||||||
return res.status(401).send({ errors: "bad credentials" });
|
|
||||||
}
|
|
||||||
|
|
||||||
const authorizedUser = { ...savedUser };
|
|
||||||
delete authorizedUser.password;
|
|
||||||
|
|
||||||
signToken(res, authorizedUser);
|
|
||||||
res.send({ ...authorizedUser }).status(200);
|
|
||||||
} catch (e) {
|
|
||||||
res.status(500).send({ errors: e });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const guest = async (req, res, next) => {
|
|
||||||
try {
|
|
||||||
// username generator returns `Guest-${num}`
|
|
||||||
const { username, password } = guestServices.generateGuest();
|
|
||||||
// generateGuestUser();
|
|
||||||
const email = null;
|
|
||||||
// id generator returns `
|
|
||||||
const id = null;
|
|
||||||
const user = { username, email, id, password };
|
|
||||||
signToken(res, user);
|
|
||||||
delete user.password;
|
|
||||||
res.send(user);
|
|
||||||
} catch (e) {
|
|
||||||
console.log(e);
|
|
||||||
res.status(500).send({ errors: e });
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
signup,
|
|
||||||
login,
|
|
||||||
guest,
|
|
||||||
};
|
|
|
@ -1,15 +0,0 @@
|
||||||
const languageArray = [
|
|
||||||
'EN'
|
|
||||||
]
|
|
||||||
|
|
||||||
exports.up = function(knex) {
|
|
||||||
return knex.schema.createTable("room", table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.string('name').notNullable().unique();
|
|
||||||
table.text('description').notNullable();
|
|
||||||
table.boolean('private').notNullable().defaultTo(false);
|
|
||||||
table.enu('language', languageArray).notNullable().defaultTo('EN');
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = knex => knex.schema.dropTableIfExists("room");
|
|
|
@ -1,15 +0,0 @@
|
||||||
const timeTypes = ['untimed','game', 'byoYomi', 'move'];
|
|
||||||
|
|
||||||
exports.up = function(knex) {
|
|
||||||
return knex.schema.createTable("time_setting", table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.enu('main_time', timeTypes).notNullable();
|
|
||||||
table.integer('time_period').notNullable(); // number of periods
|
|
||||||
table.integer('period_length').notNullable(); // length in seconds
|
|
||||||
table.enu('overtime', timeTypes).notNullable();
|
|
||||||
table.integer('overtime_period').notNullable(); // number of periods
|
|
||||||
table.integer('overtime_length').notNullable(); // length in seconds
|
|
||||||
})
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = knex => knex.schema.dropTableIfExists("time_setting");
|
|
|
@ -1,81 +0,0 @@
|
||||||
const winType = ["B+R", "B+", "B+T", "W+R", "W+", "W+T", "0", "Void", "?"];
|
|
||||||
|
|
||||||
const rankArray = [
|
|
||||||
"D9",
|
|
||||||
"D8",
|
|
||||||
"D7",
|
|
||||||
"D6",
|
|
||||||
"D5",
|
|
||||||
"D4",
|
|
||||||
"D3",
|
|
||||||
"D2",
|
|
||||||
"D1",
|
|
||||||
"K1",
|
|
||||||
"K2",
|
|
||||||
"K3",
|
|
||||||
"K4",
|
|
||||||
"K5",
|
|
||||||
"K6",
|
|
||||||
"K7",
|
|
||||||
"K8",
|
|
||||||
"K9",
|
|
||||||
"K10",
|
|
||||||
"K11",
|
|
||||||
"K12",
|
|
||||||
"K13",
|
|
||||||
"K14",
|
|
||||||
"K15",
|
|
||||||
"K16",
|
|
||||||
"K17",
|
|
||||||
"K18",
|
|
||||||
"K19",
|
|
||||||
"K20",
|
|
||||||
"K21",
|
|
||||||
"K22",
|
|
||||||
"K23",
|
|
||||||
"K24",
|
|
||||||
"K25",
|
|
||||||
"K26",
|
|
||||||
"K27",
|
|
||||||
"K28",
|
|
||||||
"K29",
|
|
||||||
"K30",
|
|
||||||
"UR",
|
|
||||||
];
|
|
||||||
|
|
||||||
exports.up = function (knex) {
|
|
||||||
return knex.schema.createTable("game", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.datetime("date");
|
|
||||||
table.float("komi").default(6.5);
|
|
||||||
table.integer("handicap").default(0);
|
|
||||||
table.integer("board_size").default(19);
|
|
||||||
table.boolean("open").default(true);
|
|
||||||
|
|
||||||
table.string("application");
|
|
||||||
table.string("application_version");
|
|
||||||
table.timestamps(true, true);
|
|
||||||
|
|
||||||
table.string("player_black");
|
|
||||||
table.string("player_white");
|
|
||||||
table.enu("player_black_rank", rankArray).default("UR");
|
|
||||||
table.enu("player_white_rank", rankArray).default("UR");
|
|
||||||
|
|
||||||
table.string("event");
|
|
||||||
table.string("name");
|
|
||||||
table.string("description");
|
|
||||||
table.integer("round");
|
|
||||||
|
|
||||||
table.enu("win_type", winType);
|
|
||||||
table.float("score");
|
|
||||||
table.integer("captures_black");
|
|
||||||
table.integer("captures_white");
|
|
||||||
|
|
||||||
table.integer("user_black").references("id").inTable("user");
|
|
||||||
table.integer("user_white").references("id").inTable("user");
|
|
||||||
table.integer("room").references("id").inTable("room");
|
|
||||||
table.integer("time_setting").references("id").inTable("time_setting");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = (knex) => knex.schema.dropTableIfExists("game");
|
|
|
@ -1,18 +0,0 @@
|
||||||
const players = ["white", "black"];
|
|
||||||
|
|
||||||
exports.up = (knex) => {
|
|
||||||
return knex.schema.createTable("move", (table) => {
|
|
||||||
table.increments("id").primary();
|
|
||||||
table.enu("player", players).notNullable();
|
|
||||||
table.integer("point_x").notNullable();
|
|
||||||
table.integer("point_y").notNullable();
|
|
||||||
table.integer("number").notNullable();
|
|
||||||
table.boolean("game_record").notNullable().default(true);
|
|
||||||
table.boolean("placement").notNullable().default(false);
|
|
||||||
|
|
||||||
table.integer("game").references("id").inTable("game").notNullable();
|
|
||||||
table.integer("prior_move").references("id").inTable("move");
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = (knex) => knex.schema.dropTableIfExists("move");
|
|
|
@ -1,13 +0,0 @@
|
||||||
exports.up = knex => {
|
|
||||||
return knex.schema.createTable("message", table => {
|
|
||||||
table.increments('id').primary();
|
|
||||||
table.timestamps(true, true)
|
|
||||||
table.text('content').notNullable();
|
|
||||||
|
|
||||||
table.integer('move').references('id').inTable('move');
|
|
||||||
table.integer('room').references('id').inTable('room');
|
|
||||||
table.integer('user').references('id').inTable('user').notNullable();
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
exports.down = knex => knex.schema.dropTableIfExists("message");
|
|
|
@ -1,97 +0,0 @@
|
||||||
const knex = require("../db");
|
|
||||||
|
|
||||||
const gameDetailSelect = [
|
|
||||||
"game.id",
|
|
||||||
"application",
|
|
||||||
"application_version",
|
|
||||||
"board_size",
|
|
||||||
"komi",
|
|
||||||
"handicap",
|
|
||||||
"open",
|
|
||||||
"win_type",
|
|
||||||
"player_black",
|
|
||||||
"player_black_rank",
|
|
||||||
"player_white",
|
|
||||||
"player_white_rank",
|
|
||||||
"captures_black",
|
|
||||||
"captures_white",
|
|
||||||
"score",
|
|
||||||
"win_type",
|
|
||||||
"description",
|
|
||||||
"event",
|
|
||||||
"round",
|
|
||||||
"name",
|
|
||||||
"room",
|
|
||||||
];
|
|
||||||
|
|
||||||
const timeSettingSelect = [
|
|
||||||
"main_time",
|
|
||||||
"time_period",
|
|
||||||
"period_length",
|
|
||||||
"overtime",
|
|
||||||
"overtime_period",
|
|
||||||
"overtime_length",
|
|
||||||
];
|
|
||||||
|
|
||||||
const gameOverviewSelect = [
|
|
||||||
"id",
|
|
||||||
"board_size",
|
|
||||||
"komi",
|
|
||||||
"handicap",
|
|
||||||
"open",
|
|
||||||
"win_type",
|
|
||||||
"player_black",
|
|
||||||
"player_black_rank",
|
|
||||||
"player_white",
|
|
||||||
"player_white_rank",
|
|
||||||
];
|
|
||||||
|
|
||||||
const findGameById = async function (gameId) {
|
|
||||||
const selection = gameDetailSelect.concat(timeSettingSelect);
|
|
||||||
|
|
||||||
const game = await knex
|
|
||||||
.from("game")
|
|
||||||
.select(selection)
|
|
||||||
.where({ "game.id": gameId })
|
|
||||||
.leftJoin("time_setting", function () {
|
|
||||||
this.on("time_setting.id", "=", "game.time_setting");
|
|
||||||
});
|
|
||||||
|
|
||||||
return game[0];
|
|
||||||
};
|
|
||||||
|
|
||||||
const findGameByRoom = async (roomId) => {
|
|
||||||
const games = await knex("game")
|
|
||||||
.where({ room: roomId })
|
|
||||||
.select(gameOverviewSelect);
|
|
||||||
|
|
||||||
return games;
|
|
||||||
};
|
|
||||||
|
|
||||||
const insertGame = async (game) => {};
|
|
||||||
|
|
||||||
const endGame = async ({ id, winType, score, bCaptures, wCaptures }) => {
|
|
||||||
try {
|
|
||||||
const game = await knex
|
|
||||||
.from("game")
|
|
||||||
.returning(gameDetailSelect)
|
|
||||||
.where({ id: id })
|
|
||||||
.update({
|
|
||||||
win_type: winType,
|
|
||||||
score: score,
|
|
||||||
captures_black: bCaptures,
|
|
||||||
captures_white: wCaptures,
|
|
||||||
open: false,
|
|
||||||
});
|
|
||||||
return game;
|
|
||||||
} catch (e) {
|
|
||||||
return e;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
findGameById,
|
|
||||||
findGameByRoom,
|
|
||||||
insertGame,
|
|
||||||
endGame,
|
|
||||||
};
|
|
|
@ -1,19 +0,0 @@
|
||||||
const knex = require('../db');
|
|
||||||
const joinUserSelect = [
|
|
||||||
'content', 'username', 'admin'
|
|
||||||
]
|
|
||||||
|
|
||||||
const findMessageByRoom = async (roomId) => {
|
|
||||||
return await knex
|
|
||||||
.from('message')
|
|
||||||
.where({'message.room': roomId})
|
|
||||||
.select(joinUserSelect)
|
|
||||||
.join('user', function() {
|
|
||||||
this.on('message.user', '=', 'user.id')
|
|
||||||
})
|
|
||||||
// .toSQL();
|
|
||||||
}
|
|
||||||
|
|
||||||
module.exports = {
|
|
||||||
findMessageByRoom
|
|
||||||
}
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue