Browse Source

missing files

master
narnaud 2 years ago
parent
commit
1f5737a2ab
  1. 24
      .gitignore
  2. 107
      README.md
  3. 131
      volumes/front/.gitignore
  4. 63
      volumes/front/public/global.css
  5. BIN
      volumes/front/public/img/pog.jpg
  6. BIN
      volumes/front/public/img/pong.png
  7. BIN
      volumes/front/public/img/profileicon.png
  8. 1
      volumes/front/public/vite.svg
  9. 82
      volumes/front/src/App.svelte
  10. 5
      volumes/front/src/app.d.ts
  11. 1
      volumes/front/src/assets/svelte.svg
  12. 80
      volumes/front/src/components/Friends.svelte
  13. 58
      volumes/front/src/components/MatchHistory.svelte
  14. 106
      volumes/front/src/components/NavBar.svelte
  15. 38
      volumes/front/src/components/Play.svelte
  16. 15
      volumes/front/src/components/Pong/Ball.ts
  17. 80
      volumes/front/src/components/Pong/Game.ts
  18. 29
      volumes/front/src/components/Pong/Paddle.ts
  19. 19
      volumes/front/src/components/Pong/Player.ts
  20. 58
      volumes/front/src/components/Pong/Pong.svelte
  21. 32
      volumes/front/src/components/Pong/constants.ts
  22. 88
      volumes/front/src/components/Pong/utils.ts
  23. 109
      volumes/front/src/components/Profile.svelte
  24. 53
      volumes/front/src/components/Spectate.svelte
  25. 8
      volumes/front/src/main.ts
  26. 2
      volumes/front/src/vite-env.d.ts

24
.gitignore

@ -1,23 +1 @@
* volumes/ssl/transcendence.pem
!.gitignore
!LICENCE
!README.md
!Makefile
!docker-compose.yml
!.env
!cont/
!cont/front
!cont/back
!cont/front/*
!cont/back/*
!volumes/
!volumes/front
!volumes/back
!volumes/front/*
!volumes/back/*

107
README.md

@ -1,107 +0,0 @@
# This repo is no longer maintained. Consider using `npm init vite` and selecting the `svelte` option or — if you want a full-fledged app framework and don't mind using pre-1.0 software — use [SvelteKit](https://kit.svelte.dev), the official application framework for Svelte.
---
# svelte app
This is a project template for [Svelte](https://svelte.dev) apps. It lives at https://github.com/sveltejs/template.
To create a new project based on this template using [degit](https://github.com/Rich-Harris/degit):
```bash
npx degit sveltejs/template svelte-app
cd svelte-app
```
*Note that you will need to have [Node.js](https://nodejs.org) installed.*
## Get started
Install the dependencies...
```bash
cd svelte-app
npm install
```
...then start [Rollup](https://rollupjs.org):
```bash
npm run dev
```
Navigate to [localhost:8080](http://localhost:8080). You should see your app running. Edit a component file in `src`, save it, and reload the page to see your changes.
By default, the server will only respond to requests from localhost. To allow connections from other computers, edit the `sirv` commands in package.json to include the option `--host 0.0.0.0`.
If you're using [Visual Studio Code](https://code.visualstudio.com/) we recommend installing the official extension [Svelte for VS Code](https://marketplace.visualstudio.com/items?itemName=svelte.svelte-vscode). If you are using other editors you may need to install a plugin in order to get syntax highlighting and intellisense.
## Building and running in production mode
To create an optimised version of the app:
```bash
npm run build
```
You can run the newly built app with `npm run start`. This uses [sirv](https://github.com/lukeed/sirv), which is included in your package.json's `dependencies` so that the app will work when you deploy to platforms like [Heroku](https://heroku.com).
## Single-page app mode
By default, sirv will only respond to requests that match files in `public`. This is to maximise compatibility with static fileservers, allowing you to deploy your app anywhere.
If you're building a single-page app (SPA) with multiple routes, sirv needs to be able to respond to requests for *any* path. You can make it so by editing the `"start"` command in package.json:
```js
"start": "sirv public --single"
```
## Using TypeScript
This template comes with a script to set up a TypeScript development environment, you can run it immediately after cloning the template with:
```bash
node scripts/setupTypeScript.js
```
Or remove the script via:
```bash
rm scripts/setupTypeScript.js
```
If you want to use `baseUrl` or `path` aliases within your `tsconfig`, you need to set up `@rollup/plugin-alias` to tell Rollup to resolve the aliases. For more info, see [this StackOverflow question](https://stackoverflow.com/questions/63427935/setup-tsconfig-path-in-svelte).
## Deploying to the web
### With [Vercel](https://vercel.com)
Install `vercel` if you haven't already:
```bash
npm install -g vercel
```
Then, from within your project folder:
```bash
cd public
vercel deploy --name my-project
```
### With [surge](https://surge.sh/)
Install `surge` if you haven't already:
```bash
npm install -g surge
```
Then, from within your project folder:
```bash
npm run build
surge public my-project.surge.sh
```

131
volumes/front/.gitignore

@ -0,0 +1,131 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*

63
volumes/front/public/global.css

@ -0,0 +1,63 @@
html, body {
position: relative;
width: 100%;
height: 100%;
}
body {
color: #333;
margin: 0;
padding: 8px;
box-sizing: border-box;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
}
a {
color: rgb(0,100,200);
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
a:visited {
color: rgb(0,80,160);
}
label {
display: block;
}
input, button, select, textarea {
font-family: inherit;
font-size: inherit;
-webkit-padding: 0.4em 0;
padding: 0.4em;
margin: 0 0 0.5em 0;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 2px;
}
input:disabled {
color: #ccc;
}
button {
color: #333;
background-color: #f4f4f4;
outline: none;
}
button:disabled {
color: #999;
}
button:not(:disabled):active {
background-color: #ddd;
}
button:focus {
border-color: #666;
}

BIN
volumes/front/public/img/pog.jpg

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.1 KiB

BIN
volumes/front/public/img/pong.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

BIN
volumes/front/public/img/profileicon.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.0 KiB

1
volumes/front/public/vite.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

82
volumes/front/src/App.svelte

@ -0,0 +1,82 @@
<script lang="ts">
import Navbar from './components/NavBar.svelte';
import Profile from './components/Profile.svelte';
import MatchHistory from './components/MatchHistory.svelte';
import type { Match } from './components/MatchHistory.svelte';
import Friends from './components/Friends.svelte';
import type { Friend } from './components/Friends.svelte';
import Spectate from './components/Spectate.svelte';
import type { SpectateType } from './components/Spectate.svelte';
import Play from './components/Play.svelte';
import Pong from './components/Pong/Pong.svelte';
let isProfileOpen = false;
function clickProfile() {
isProfileOpen = true;
}
let isHistoryOpen = false;
function clickHistory() {
isHistoryOpen = true;
}
let matches: Array<Match> = [
{ winner: 'Alice', loser: 'Bob', points: -5, rank: '22' },
{ winner: 'Alice', loser: 'Bob', points: 10, rank: '24' },
{ winner: 'Alice', loser: 'Bob', points: 10, rank: '24' },
{ winner: 'Alice', loser: 'Bob', points: 7, rank: '23' },
{ winner: 'Alice', loser: 'Bob', points: 10, rank: '24' },
{ winner: 'Alice', loser: 'Bob', points: 10, rank: '24' }
];
let isFriendOpen = false;
function clickFriends() {
isFriendOpen = true;
}
let friends: Array<Friend> = [
{ username: 'Alice', status: 'online' },
{ username: 'Bob', status: 'online' },
{ username: 'Charlie', status: 'offline' },
{ username: 'Dave', status: 'offline' },
{ username: 'Eve', status: 'in a game' },
{ username: 'Frank', status: 'online' }
];
let isSpectateOpen = false;
function clickSpectate() {
isSpectateOpen = true;
}
let spectate: Array<SpectateType> = [
{ player1: 'Alice', player2: 'Bob', id: '1' },
{ player1: 'Alice', player2: 'Bob', id: '4' },
{ player1: 'Alice', player2: 'Bob', id: '6' },
{ player1: 'Alice', player2: 'Bob', id: '8' },
{ player1: 'Alice', player2: 'Bob', id: '2' },
{ player1: 'Alice', player2: 'Bob', id: '3' }
];
</script>
<main>
<Navbar {clickProfile} {clickHistory} {clickFriends} {clickSpectate} />
{#if isSpectateOpen}
<div on:click={() => (isSpectateOpen = false)} on:keydown={() => (isSpectateOpen = false)}>
<Spectate {spectate} />
</div>
{/if}
{#if isFriendOpen}
<div on:click={() => (isFriendOpen = false)} on:keydown={() => (isFriendOpen = false)}>
<Friends {friends} />
</div>
{/if}
{#if isHistoryOpen}
<div on:click={() => (isHistoryOpen = false)} on:keydown={() => (isHistoryOpen = false)}>
<MatchHistory {matches} />
</div>
{/if}
{#if isProfileOpen}
<div on:click={() => (isProfileOpen = false)} on:keydown={() => (isProfileOpen = false)}>
<Profile username="Alice" wins={10} losses={5} elo={256} rank={23} is2faEnabled={false} />
</div>
{/if}
<Play />
<Pong />
</main>
<style>
</style>

5
volumes/front/src/app.d.ts

@ -0,0 +1,5 @@
declare global {
namespace App {
}
}
export {};

1
volumes/front/src/assets/svelte.svg

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="26.6" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 308"><path fill="#FF3E00" d="M239.682 40.707C211.113-.182 154.69-12.301 113.895 13.69L42.247 59.356a82.198 82.198 0 0 0-37.135 55.056a86.566 86.566 0 0 0 8.536 55.576a82.425 82.425 0 0 0-12.296 30.719a87.596 87.596 0 0 0 14.964 66.244c28.574 40.893 84.997 53.007 125.787 27.016l71.648-45.664a82.182 82.182 0 0 0 37.135-55.057a86.601 86.601 0 0 0-8.53-55.577a82.409 82.409 0 0 0 12.29-30.718a87.573 87.573 0 0 0-14.963-66.244"></path><path fill="#FFF" d="M106.889 270.841c-23.102 6.007-47.497-3.036-61.103-22.648a52.685 52.685 0 0 1-9.003-39.85a49.978 49.978 0 0 1 1.713-6.693l1.35-4.115l3.671 2.697a92.447 92.447 0 0 0 28.036 14.007l2.663.808l-.245 2.659a16.067 16.067 0 0 0 2.89 10.656a17.143 17.143 0 0 0 18.397 6.828a15.786 15.786 0 0 0 4.403-1.935l71.67-45.672a14.922 14.922 0 0 0 6.734-9.977a15.923 15.923 0 0 0-2.713-12.011a17.156 17.156 0 0 0-18.404-6.832a15.78 15.78 0 0 0-4.396 1.933l-27.35 17.434a52.298 52.298 0 0 1-14.553 6.391c-23.101 6.007-47.497-3.036-61.101-22.649a52.681 52.681 0 0 1-9.004-39.849a49.428 49.428 0 0 1 22.34-33.114l71.664-45.677a52.218 52.218 0 0 1 14.563-6.398c23.101-6.007 47.497 3.036 61.101 22.648a52.685 52.685 0 0 1 9.004 39.85a50.559 50.559 0 0 1-1.713 6.692l-1.35 4.116l-3.67-2.693a92.373 92.373 0 0 0-28.037-14.013l-2.664-.809l.246-2.658a16.099 16.099 0 0 0-2.89-10.656a17.143 17.143 0 0 0-18.398-6.828a15.786 15.786 0 0 0-4.402 1.935l-71.67 45.674a14.898 14.898 0 0 0-6.73 9.975a15.9 15.9 0 0 0 2.709 12.012a17.156 17.156 0 0 0 18.404 6.832a15.841 15.841 0 0 0 4.402-1.935l27.345-17.427a52.147 52.147 0 0 1 14.552-6.397c23.101-6.006 47.497 3.037 61.102 22.65a52.681 52.681 0 0 1 9.003 39.848a49.453 49.453 0 0 1-22.34 33.12l-71.664 45.673a52.218 52.218 0 0 1-14.563 6.398"></path></svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

80
volumes/front/src/components/Friends.svelte

@ -0,0 +1,80 @@
<script lang="ts" context="module">
export interface Friend {
username: string;
status: 'online' | 'offline' | 'in a game';
}
</script>
<script lang="ts">
export let friends: Array<Friend> = [];
async function addFriend(event: any) {
console.log(typeof event);
event.preventDefault();
const usernameInput = event.target.querySelector('input[type="text"]');
console.log(usernameInput);
const username = usernameInput.value;
// const response = await fetch('', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({ username })
// });
// if (response.ok) {
// console.log('Friend added successfully');
// } else {
// console.log('Failed to add friend');
// }
// usernameInput.value = '';
alert('Trying to add friend' + username);
}
</script>
<div class="overlay">
<div class="friends" on:click|stopPropagation on:keydown|stopPropagation>
<div>
{#if friends.length > 0}
<h2>Monkey friends</h2>
{#each friends.slice(0, 10) as friend}
<li>
<span>{friend.username} is {friend.status}</span>
</li>
{/each}
{:else}
<p>No friends to display</p>
{/if}
<div>
<h3>Add a friend</h3>
<form on:submit={addFriend}>
<input type="text" />
<button type="submit">Add</button>
</form>
</div>
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
.friends {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
}
</style>

58
volumes/front/src/components/MatchHistory.svelte

@ -0,0 +1,58 @@
<script lang="ts" context="module">
export interface Match {
winner: string;
loser: string;
points: number;
rank: string;
}
</script>
<script lang="ts">
export let matches: Array<Match> = [];
</script>
<div class="overlay">
<div class="history" on:click|stopPropagation on:keydown|stopPropagation>
<div>
{#if matches.length > 0}
<h2>Last 10 monkey games</h2>
{#each matches.slice(0, 10) as match}
<li>
<span>{match.winner} 1 - 0 {match.loser}</span>
{#if match.points > 0}
<span>+{match.points}</span>
{:else}
<span>{match.points}</span>
{/if}
<span>MP | rank #{match.rank}</span>
</li>
{/each}
{:else}
<p>No matches to display</p>
{/if}
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
.history {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
}
</style>

106
volumes/front/src/components/NavBar.svelte

@ -0,0 +1,106 @@
<script lang="ts">
export let links = [
{ text: 'Home', url: 'img/pong.png' },
{ text: 'Spectate' },
{ text: 'Chat' },
{ text: 'History' },
{ text: 'Friends' },
{ text: 'Profile' }
];
export let clickProfile = () => {};
export let clickHistory = () => {};
export let clickFriends = () => {};
export let clickSpectate = () => {};
</script>
<nav class="navigation-bar">
<ul>
{#each links as link}
{#if link.text === 'Spectate'}
<li>
<button on:click={clickSpectate}>
<p>Spectate</p>
</button>
</li>
{/if}
{#if link.text === 'Friends'}
<li>
<button on:click={clickFriends}>
<p>Friends</p>
</button>
</li>
{/if}
{#if link.text === 'Profile'}
<li>
<button on:click={clickProfile}>
<img src="img/profileicon.png" alt="profile icon" />
</button>
</li>
{/if}
{#if link.text === 'History'}
<li>
<button on:click={clickHistory}>
<p>History</p>
</button>
</li>
{/if}
{#if link.text === 'Home'}
<li>
<a href="/">
<img src="img/pong.png" alt="home-icon" />
</a>
</li>
{/if}
{/each}
</ul>
</nav>
<style>
.navigation-bar {
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
padding: 1rem;
}
.navigation-bar ul {
display: flex;
justify-content: center;
align-items: center;
list-style: none;
padding: 0;
}
.navigation-bar li {
margin: 0 1rem;
}
.navigation-bar img {
width: 2rem;
height: 2rem;
}
.navigation-bar button {
border: none;
}
@media (max-width: 768px) {
.navigation-bar {
flex-direction: column;
align-items: stretch;
}
.navigation-bar ul {
flex-direction: column;
align-items: center;
justify-content: center;
}
.navigation-bar li {
margin: 0;
padding: 1rem;
text-align: center;
}
}
</style>

38
volumes/front/src/components/Play.svelte

@ -0,0 +1,38 @@
<script lang="ts">
// import { navigate } from 'svelte-routing';
// function handleMatchmakingClick() {
// navigate('/matchmaking');
// }
// function handlePlayWithFriendClick() {
// navigate('/play-with-friend');
// }
</script>
<!-- dr -->
<main>
<h1>Choose a gamemode</h1>
<!-- <button on:click={handleMatchmakingClick}>Matchmaking</button>
<button on:click={handlePlayWithFriendClick}>Play with a friend</button> -->
<button>Matchmaking</button>
<button>Play with a friend</button>
</main>
<style>
main {
display: flex;
flex-direction: column;
align-items: center;
}
h1 {
margin-bottom: 2rem;
}
button {
font-size: 1.5rem;
padding: 1rem 2rem;
margin-bottom: 1rem;
}
</style>

15
volumes/front/src/components/Pong/Ball.ts

@ -0,0 +1,15 @@
import { Point, Rect } from './utils';
export class Ball {
rect: Rect;
speed: Point;
color: string | CanvasGradient | CanvasPattern = 'white';
constructor(spawn: Point, size: Point = new Point(20, 20), speed: Point = new Point(10, 2)) {
this.rect = new Rect(spawn, size);
}
draw(context: CanvasRenderingContext2D) {
this.rect.draw(context, this.color);
}
}

80
volumes/front/src/components/Pong/Game.ts

@ -0,0 +1,80 @@
import { Ball } from './Ball';
import { GAME_EVENTS } from './constants';
import type { GameInfo, GameUpdate } from './constants';
import { Paddle } from './Paddle';
import { Player } from './Player';
import { formatWebsocketData, Point } from './utils';
const BG_COLOR = 'black';
export class Game {
canvas: HTMLCanvasElement;
context: CanvasRenderingContext2D;
ball: Ball;
players: Player[];
my_paddle: Paddle;
constructor(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) {
this.canvas = canvas;
this.context = context;
this.players = [];
this.my_paddle = null;
}
setInfo(data: GameInfo) {
this.canvas.width = data.mapSize.x;
this.canvas.height = data.mapSize.y;
this.ball = new Ball(new Point(this.canvas.width / 2, this.canvas.height / 2), data.ballSize);
const paddle1: Paddle = new Paddle(new Point(data.playerXOffset, this.canvas.height / 2), data.paddleSize);
const paddle2: Paddle = new Paddle(
new Point(this.canvas.width - data.playerXOffset, this.canvas.height / 2),
data.paddleSize
);
this.players = [new Player(paddle1), new Player(paddle2)];
this.my_paddle = this.players[data.yourPaddleIndex].paddle;
}
start(socket: WebSocket) {
if (this.my_paddle) {
this.canvas.addEventListener('mousemove', (e) => {
this.my_paddle.move(e);
socket.send(
formatWebsocketData(GAME_EVENTS.PLAYER_MOVE, {
position: this.my_paddle.rect.center
})
);
});
console.log('Game started!');
}
}
update(data: GameUpdate) {
if (this.players[0].paddle != this.my_paddle) {
this.players[0].paddle.rect.center = data.paddlesPositions[0];
}
if (this.players[1].paddle != this.my_paddle) {
this.players[1].paddle.rect.center = data.paddlesPositions[1];
}
this.ball.rect.center = data.ballPosition;
for (let i = 0; i < data.scores.length; i++) {
this.players[i].score = data.scores[i];
}
}
draw() {
this.context.fillStyle = BG_COLOR;
this.context.fillRect(0, 0, this.canvas.width, this.canvas.height);
this.players.forEach((p) => p.draw(this.context));
this.ball.draw(this.context);
const max_width = 50;
this.context.font = '50px Arial';
const text_width = this.context.measureText('0').width;
const text_offset = 50;
this.players[0].drawScore(this.canvas.width / 2 - (text_width + text_offset), max_width, this.context);
this.players[1].drawScore(this.canvas.width / 2 + text_offset, max_width, this.context);
}
}

29
volumes/front/src/components/Pong/Paddle.ts

@ -0,0 +1,29 @@
import { Point, Rect } from './utils';
export class Paddle {
rect: Rect;
color: string | CanvasGradient | CanvasPattern = 'white';
constructor(spawn: Point, size: Point = new Point(6, 100)) {
this.rect = new Rect(spawn, size);
}
draw(context: CanvasRenderingContext2D) {
this.rect.draw(context, this.color);
}
move(e: MouseEvent) {
const canvas = e.target as HTMLCanvasElement;
const rect = canvas.getBoundingClientRect();
const new_y = ((e.clientY - rect.top) * canvas.height) / rect.height;
const offset: number = this.rect.size.y / 2;
if (new_y - offset < 0) {
this.rect.center.y = offset;
} else if (new_y + offset > canvas.height) {
this.rect.center.y = canvas.height - offset;
} else {
this.rect.center.y = new_y;
}
}
}

19
volumes/front/src/components/Pong/Player.ts

@ -0,0 +1,19 @@
import type { Paddle } from './Paddle';
export class Player {
paddle: Paddle;
score: number;
constructor(paddle: Paddle) {
this.paddle = paddle;
this.score = 0;
}
draw(context: CanvasRenderingContext2D) {
this.paddle.draw(context);
}
drawScore(score_position_x: number, max_width: number, context: CanvasRenderingContext2D) {
context.fillText(this.score.toString(), score_position_x, 50, max_width);
}
}

58
volumes/front/src/components/Pong/Pong.svelte

@ -0,0 +1,58 @@
<script lang="ts">
import { GAME_EVENTS } from './constants';
import { Game } from './Game';
import { formatWebsocketData } from './utils';
import { onMount } from 'svelte'
const FPS = 144;
const SERVER_URL = 'ws://localhost:3001';
let socket: WebSocket;
onMount(async () => {
//Get canvas and its context
window.onload = () => {
const canvas: HTMLCanvasElement = document.getElementById('pong_canvas') as HTMLCanvasElement;
if (canvas) {
const context: CanvasRenderingContext2D = canvas.getContext('2d') as CanvasRenderingContext2D;
if (context) {
setupSocket(canvas, context);
}
}
};
})
function setupSocket(canvas: HTMLCanvasElement, context: CanvasRenderingContext2D) {
socket = new WebSocket(SERVER_URL);
const game = new Game(canvas, context);
socket.onmessage = function (e) {
const event_json = JSON.parse(e.data);
const event = event_json.event;
const data = event_json.data;
if (event == GAME_EVENTS.START_GAME) {
game.start(socket);
} else if (event == GAME_EVENTS.GAME_TICK) {
game.update(data);
} else if (event == GAME_EVENTS.GET_GAME_INFO) {
game.setInfo(data);
setInterval(() => {
game.draw();
}, 1000 / FPS);
console.log('Game loaded!');
} else {
console.log('Unknown event from server: ' + event);
}
};
socket.onopen = () => {
console.log('Connected to game server!');
socket.send(formatWebsocketData(GAME_EVENTS.GET_GAME_INFO));
};
}
</script>
<div>
<button on:click={() => socket.send(formatWebsocketData(GAME_EVENTS.START_GAME))}>Start game</button>
<br />
<br />
<canvas id="pong_canvas" />
</div>

32
volumes/front/src/components/Pong/constants.ts

@ -0,0 +1,32 @@
import { Point } from './utils';
export const GAME_EVENTS = {
START_GAME: 'START_GAME',
GAME_TICK: 'GAME_TICK',
PLAYER_MOVE: 'PLAYER_MOVE',
GET_GAME_INFO: 'GET_GAME_INFO'
};
export interface GameInfo extends GameInfoConstants {
yourPaddleIndex: number;
}
export interface GameInfoConstants {
mapSize: Point;
paddleSize: Point;
playerXOffset: number;
ballSize: Point;
winScore: number;
}
export const gameInfoConstants: GameInfoConstants = {
mapSize: new Point(600, 400),
paddleSize: new Point(6, 50),
playerXOffset: 50,
ballSize: new Point(20, 20),
winScore: 2
};
export interface GameUpdate {
paddlesPositions: Point[];
ballPosition: Point;
scores: number[];
}

88
volumes/front/src/components/Pong/utils.ts

@ -0,0 +1,88 @@
export class Point {
x: number;
y: number;
constructor(x: number, y: number) {
this.x = x;
this.y = y;
}
//Returns a new point
add(other: Point) {
return new Point(this.x + other.x, this.y + other.y);
}
//Modifies `this` point
add_inplace(other: Point) {
this.x += other.x;
this.y += other.y;
}
clone(): Point {
return new Point(this.x, this.y);
}
}
export class Rect {
center: Point;
size: Point;
constructor(center: Point, size: Point) {
this.center = center;
this.size = size;
}
draw(context: CanvasRenderingContext2D, color: string | CanvasGradient | CanvasPattern) {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2);
context.fillStyle = color;
context.fillRect(this.center.x - offset.x, this.center.y - offset.y, this.size.x, this.size.y);
}
//True if `this` rect contains `other` rect in the x-axis
contains_x(other: Rect): boolean {
const offset: number = this.size.x / 2;
const offset_other: number = other.size.x / 2;
if (
this.center.x - offset <= other.center.x - offset_other &&
this.center.x + offset >= other.center.x + offset_other
)
return true;
return false;
}
//True if `this` rect contains `other` rect in the y-axis
contains_y(other: Rect): boolean {
const offset: number = this.size.y / 2;
const offset_other: number = other.size.y / 2;
if (
this.center.y - offset <= other.center.y - offset_other &&
this.center.y + offset >= other.center.y + offset_other
)
return true;
return false;
}
collides(other: Rect): boolean {
const offset: Point = new Point(this.size.x / 2, this.size.y / 2);
const offset_other: Point = new Point(other.size.x / 2, other.size.y / 2);
if (
this.center.x - offset.x < other.center.x + offset_other.x &&
this.center.x + offset.x > other.center.x - offset_other.x &&
this.center.y - offset.y < other.center.y + offset_other.y &&
this.center.y + offset.y > other.center.y - offset_other.y
)
return true;
return false;
}
}
export function formatWebsocketData(event: string, data?: any): string {
return JSON.stringify({
event,
data
});
}

109
volumes/front/src/components/Profile.svelte

@ -0,0 +1,109 @@
<script lang="ts">
export let username = '';
export let realname = '';
export let wins = 0;
export let losses = 0;
export let elo = 0;
export let rank = -1;
export let is2faEnabled = false;
async function handleSubmit(event: Event) {
event.preventDefault();
// const response = await fetch('', {
// method: 'POST',
// headers: {
// 'Content-Type': 'application/json'
// },
// body: JSON.stringify({
// username
// })
// });
// if (response.ok) {
// console.log('username updated');
// }
// else {
// console.log('username update failed');
// }
alert('Trying to update username to ' + username);
}
async function handleAvatarUpload(event: Event) {
event.preventDefault();
alert('Trying to upload avatar');
}
async function handle2fa(event: Event) {
event.preventDefault();
alert('Trying to ' + (is2faEnabled ? 'disable' : 'enable') + ' 2FA');
}
</script>
<div class="overlay">
<div class="profile" on:click|stopPropagation on:keydown|stopPropagation>
<div class="profile-header">
<img class="profile-img" src="img/profileicon.png" alt="Profile Icon" />
<h3>{realname}</h3>
<form on:submit={handleAvatarUpload}>
<button type="submit">Upload Avatar</button>
</form>
</div>
<div class="profile-body">
<form on:submit={handleSubmit}>
<div class="username">
<label for="username">Username</label>
<input type="text" id="username" bind:value={username} />
</div>
<button type="submit">Submit</button>
</form>
<p>Wins: {wins}</p>
<p>Losses: {losses}</p>
<p>Winrate: {(wins / (wins + losses)) * 100}%</p>
<p>Elo : {elo}</p>
<p>Rank: {rank}</p>
<form class="two-factor-auth" on:submit={handle2fa}>
<button type="submit">
{#if is2faEnabled}
Disable 2FA
{:else}
Enable 2FA
{/if}
</button>
</form>
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
.profile {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
}
.profile-header {
display: flex;
align-items: center;
}
.profile-img {
width: 50px;
height: 50px;
margin-right: 1rem;
}
.two-factor-auth {
margin-top: 1rem;
}
</style>

53
volumes/front/src/components/Spectate.svelte

@ -0,0 +1,53 @@
<script lang="ts" context="module">
export interface SpectateType {
id: string;
player1: string;
player2: string;
}
</script>
<script lang="ts">
export let spectate: Array<SpectateType> = [];
export let watch = () => {};
</script>
<div class="overlay">
<div class="spectate" on:click|stopPropagation on:keydown|stopPropagation>
<div>
{#if spectate.length > 0}
<h2>Monkey spectating</h2>
{#each spectate.slice(0, 10) as _spectate}
<li>
<span>{_spectate.player1} VS {_spectate.player2}</span>
<button on:click={() => watch(_spectate.id)}>Spectate</button>
</li>
{/each}
{:else}
<p>No matches to spectate</p>
{/if}
</div>
</div>
</div>
<style>
.overlay {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 9998;
display: flex;
justify-content: center;
align-items: center;
}
.spectate {
background-color: #fff;
border: 1px solid #ccc;
border-radius: 5px;
padding: 1rem;
width: 300px;
}
</style>

8
volumes/front/src/main.ts

@ -0,0 +1,8 @@
import App from './App.svelte';
const app = new App({
target: document.body,
});
export const prerender = true;
export default app;

2
volumes/front/src/vite-env.d.ts

@ -0,0 +1,2 @@
/// <reference types="svelte" />
/// <reference types="vite/client" />
Loading…
Cancel
Save