narnaud
2 years ago
26 changed files with 1059 additions and 130 deletions
@ -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/* |
|
||||
|
|
||||
|
@ -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 |
|
||||
``` |
|
@ -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.* |
@ -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; |
||||
|
} |
After Width: | Height: | Size: 9.1 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 9.0 KiB |
After Width: | Height: | Size: 1.5 KiB |
@ -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> |
@ -0,0 +1,5 @@ |
|||||
|
declare global { |
||||
|
namespace App { |
||||
|
} |
||||
|
} |
||||
|
export {}; |
After Width: | Height: | Size: 1.9 KiB |
@ -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> |
@ -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> |
@ -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> |
@ -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> |
@ -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); |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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; |
||||
|
} |
||||
|
} |
||||
|
} |
@ -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); |
||||
|
} |
||||
|
} |
@ -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> |
@ -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[]; |
||||
|
} |
@ -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 |
||||
|
}); |
||||
|
} |
@ -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> |
@ -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> |
@ -0,0 +1,8 @@ |
|||||
|
import App from './App.svelte'; |
||||
|
|
||||
|
const app = new App({ |
||||
|
target: document.body, |
||||
|
}); |
||||
|
|
||||
|
export const prerender = true; |
||||
|
export default app; |
@ -0,0 +1,2 @@ |
|||||
|
/// <reference types="svelte" />
|
||||
|
/// <reference types="vite/client" />
|
Loading…
Reference in new issue