Skip to content

Commit

Permalink
Merge pull request #14 from cygni/web-facelift
Browse files Browse the repository at this point in the history
Web facelift
  • Loading branch information
Dolvur authored Jul 27, 2022
2 parents 792ce7e + 13024b2 commit 356eec3
Show file tree
Hide file tree
Showing 53 changed files with 3,044 additions and 2,546 deletions.
3 changes: 2 additions & 1 deletion .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
NODE_ENV=development
API_URL=http://localhost:8080
API_URL=http://localhost:8080
PORT=8090
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -150,4 +150,8 @@ sketch
# Build
build

# End of https://www.toptal.com/developers/gitignore/api/react,node
# End of https://www.toptal.com/developers/gitignore/api/react,node

# runtime env

runtime-env.js
25 changes: 22 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,45 @@
# Snakebot Reactclient

This is the webclient for the Cygni snakebot tournament written in React with TypeScript. This application communicates with a [snakebot game server](https://github.com/cygni/snakebot) using a websocket.

#

## Users

If you are a user who **only** wants to code your own bot, then simply head to the [snakebot client respository](https://github.com/cygni/snakebot-client-js) and follow the instructions there. There will be a *docker-compose* file there to easily get your own server and webclient running as containers without the need to clone them from here.
If you are a user who **only** wants to code your own bot, then simply head to the [snakebot client respository](https://github.com/cygni/snakebot-client-js) and follow the instructions there. There will be a _docker-compose_ file there to easily get your own server and webclient running as containers without the need to clone them from here.

#

## Maintainers

### Requirements
* Node.js >= 16.15.1
* npm >= 8.11.0

- Node.js >= 16.15.1
- npm >= 8.11.0

#

### To get the development server running locally

After cloning the repository, open a terminal inside the root folder and run the following commands:

```
> npm install
> npm start
```

The server should now be running on http://localhost:8090.

#

### **Updates and Docker**

**IMPORTANT**: Commits on the **main** branch will launch an action that builds and **overrides** the docker image tagged as the latest on [DockerHub](https://hub.docker.com/repository/docker/cygni/snakebot-reactclient). Therefore it is important to **ONLY** push changes to **main** that works and have been tested, to ensure that latest image works for anyone who wants to use it. If a commit is deemed **stable** you can also add a **tag** to that commit to ensure it remains on [DockerHub](https://hub.docker.com/repository/docker/cygni/snakebot-reactclient) without getting overwritten. E.g creating a release with a tag will both push the newly build docker image with the tag latest **AND** the tag name given as long as it follows the standard **X.X.X** name.

Because of what mentioned above, when adding a new feature or changing some behavior, make sure to work on a **different branch** first before pushing to **main**.

### **Production**

**IMPORTANT**: Commits to the main branch will also act as commits towards production. Rebuilding the images on DockerHub through commits from main will cause the production server to reboot with the updated version of the image.

#
7 changes: 4 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
{
"name": "snakebot-webclient-2.0",
"version": "0.1.0",
"homepage": "https://dolvur.github.io/",
"name": "snakebot-reactclient",
"description": "A new webclient for Snakebot",
"version": "1.0.0",
"contributors": ["Daniel Karlsson, Sebastian Helin", "Olivia Harlin"],
"private": true,
"dependencies": {
"@reduxjs/toolkit": "^1.8.2",
Expand Down
Binary file added public/favicon-old.ico
Binary file not shown.
Binary file modified public/favicon.ico
Binary file not shown.
2 changes: 1 addition & 1 deletion public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
content="Viewing AI snakegames and hosting tournaments"
/>
<title>Snakebot</title>
</head>
Expand Down
1 change: 0 additions & 1 deletion public/runtime-env.js

This file was deleted.

14 changes: 5 additions & 9 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,13 @@
import './stylesheet.scss';
import { store } from './context/store';
import { Routes, Route, BrowserRouter } from 'react-router-dom';
import { Provider } from 'react-redux';
import axios from 'axios';
import PageHeader from './components/PageHeader';
import PageFooter from './components/PageFooter';
import HomeView from './views/HomeView';
import AboutView from './views/AboutView';
import GettingStartedView from './views/GettingStartedView';
import StartView from './views/StartView';
import GamesearchView from './views/GamesearchView';
import axios from 'axios';
import GameboardView from './views/GameboardView';
import { Provider } from 'react-redux';
import TournamentView from './views/TournamentView';
import LoginView from './views/LoginView'
import Constants from './constants/Arbitraryconstants';
Expand All @@ -23,17 +21,15 @@ function App() {
<BrowserRouter>
<PageHeader/>
<Routes>
<Route path="/" element={<HomeView />}></Route>
<Route path="/about" element={<AboutView />}></Route>
<Route path="/getting-started" element={<GettingStartedView />}></Route>
<Route path="/" element={<StartView />}></Route>
<Route path="/viewgame" element={<GamesearchView />}></Route>
<Route path="/viewgame/:gameID" element={<GameboardView />}></Route>
<Route path="/tournament" element={<TournamentView />}></Route>
<Route path="/tournament/:gameID" element={<GameboardView />}></Route>
<Route path="/login" element={<LoginView/>}></Route>
</Routes>
<PageFooter/>
</BrowserRouter>
<PageFooter/>
</Provider>
);
}
Expand Down
244 changes: 139 additions & 105 deletions src/api.ts
Original file line number Diff line number Diff line change
@@ -1,120 +1,154 @@
import axios from "axios";
import { Console } from "console";
import SockJS from "sockjs-client";
import Arbitraryconstants from "./constants/Arbitraryconstants";
import MessageTypes, { GameSettings } from "./constants/messageTypes"
import { onSocketMessage } from "./context/messageDispatch";
import { GameData } from "./context/slices/gameDataSlice";

const socket = new SockJS(Arbitraryconstants.SERVER_URL + "/events");
import axios from 'axios';
import SockJS from 'sockjs-client';
import Arbitraryconstants from './constants/Arbitraryconstants';
import { GameSettings } from './constants/messageTypes';
import { onSocketMessage } from './context/messageDispatch';
import { GameData } from './context/slices/gameDataSlice';
import { clearTournament } from './context/slices/tournamentSlice';
import { store } from './context/store';

let socket = new SockJS(Arbitraryconstants.SERVER_URL + '/events');
let onConnectQueue: string[] = [];
newConnection();

function newConnection() {
socket = new SockJS(Arbitraryconstants.SERVER_URL + '/events');

socket.onopen = () => {
console.log('Connected to server');
// Send all queued messages
onConnectQueue.forEach((msg) => {
socket.send(msg);
});
onConnectQueue = [];
};

socket.onmessage = (event: any) => onSocketMessage(event.data);

socket.onclose = () => {
console.log('Disconnected from server');
localStorage.clear();
store.dispatch(clearTournament());

setTimeout(() => {
console.log('Trying to reconnect...');
newConnection();
}, 3000);
};
}

function sendWhenConnected(msg: string) {
console.log("Queuing/sending socket message:", JSON.parse(msg));
if (socket.readyState === 1 && localStorage.getItem("token") !== null) {
socket.send(msg);
} else {
onConnectQueue.push(msg); // if no connection is established, save message in queue
}
console.log('Queuing/sending socket message:', JSON.parse(msg));
if (socket.readyState === 1 && localStorage.getItem('token') !== null) {
socket.send(msg);
} else {
onConnectQueue.push(msg); // if no connection is established, save message in queue
}
}

// ##############################################################################################
// ########################### REST API ########################################################
// ##############################################################################################
export async function getToken(username: string, password: string): Promise<{ success: boolean; data: string }> {
try {
let resp = await axios.get(`/login?login=${username}&password=${password}`);
return { success: true, data: resp.data };
} catch (error: any) {
console.error('Error getting token:', error);
return { success: false, data: typeof error.response.data === 'string' ? error.response.data : error.message };
}
}

export async function getToken(username: string, password: string): Promise<{success: boolean, data: string}> {
try {
let resp = await axios.get(`/login?login=${username}&password=${password}`)
return {success: true, data: resp.data};
} catch (error: any) {
console.error("Error getting token:", error);
return {success: false, data: typeof(error.response.data)==="string" ? error.response.data : error.message};
}
export type Game = {
gameDate: string;
gameId: string;
players: string[];
};

async function searchForGames(snakeName: string): Promise<Game[]> {
const resp = await axios.get(`/history/search/${snakeName}`).catch((err) => {
console.error(err);
});
return resp ? resp.data.items : [];
}

async function getGame(gameId: string): Promise<GameData> {
const resp = await axios.get(`/history/${gameId}`).catch((err) => {
console.error(err);
});
return resp ? resp.data : {};
}

socket.onopen = () => {
console.log("Connected to server");
// Send all queued messages
onConnectQueue.forEach(msg => {
socket.send(msg);
});
onConnectQueue = [];
// ##############################################################################################
// ###################### SOCKET FUNCTIONS #####################################################
// ##############################################################################################
async function createTournament(tournamentName: string): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.CreateTournament',
token: localStorage.getItem('token'),
tournamentName: tournamentName,
})
);
}

socket.onmessage = (event: any) => onSocketMessage(event.data);
async function killTournament(): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.KillTournament',
token: localStorage.getItem('token'),
tournamentId: 'NOT_IMPLEMENTED',
})
);
}

socket.onclose = () => {
console.log("Disconnected from server");
async function getActiveTournament(): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.GetActiveTournament',
token: localStorage.getItem('token'),
})
);
}

export type Game = {
gameDate: string,
gameId: string,
players: string[],
async function startTournament(tournamentId: string): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.StartTournament',
token: localStorage.getItem('token'),
tournamentId: tournamentId,
})
);
}

async function startTournamentGame(gameId: string): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.StartGame',
gameId: gameId,
})
);
}

async function updateTournamentSettings(gameSettings: GameSettings): Promise<void> {
sendWhenConnected(
JSON.stringify({
type: 'se.cygni.snake.eventapi.request.UpdateTournamentSettings',
token: localStorage.getItem('token'),
gameSettings: gameSettings,
})
);
}

export default {

// ##############################################################################################
// ########################### REST API ########################################################
// ##############################################################################################
async searchForGames(snakeName: string): Promise<Game[]> {
const resp = await axios.get(`/history/search/${snakeName}`).catch(err => {
console.error(err);
});
return resp ? resp.data.items: [];
},

async getGame(gameId: string): Promise<GameData> {
const resp = await axios.get(`/history/${gameId}`).catch(err => {
console.error(err);
});
return resp ? resp.data: {};
},

// ##############################################################################################
// ###################### SOCKET FUNCTIONS #####################################################
// ##############################################################################################
async createTournament(tournamentName: string): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.CreateTournament',
token: localStorage.getItem("token"),
tournamentName: tournamentName,
}));
},

async killTournament(): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.KillTournament',
token: localStorage.getItem("token"),
tournamentId: 'NOT_IMPLEMENTED',
}));
},

async getActiveTournament(): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.GetActiveTournament',
token: localStorage.getItem("token"),
}));
},

async startTournament(tournamentId: string): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.StartTournament',
token: localStorage.getItem("token"),
tournamentId: tournamentId,
}));
},

async startTournamentGame(gameId: string): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.StartGame',
gameId: gameId,
}));
},

async updateTournamentSettings(gameSettings: GameSettings): Promise<void> {
sendWhenConnected(JSON.stringify({
type: 'se.cygni.snake.eventapi.request.UpdateTournamentSettings',
token: localStorage.getItem("token"),
gameSettings: gameSettings,
}));
},
};
const api = {
searchForGames,
getGame,
createTournament,
killTournament,
getActiveTournament,
startTournament,
startTournamentGame,
updateTournamentSettings,
};

export default api;
3 changes: 3 additions & 0 deletions src/assets/icons/search-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 356eec3

Please sign in to comment.