🎉
This commit is contained in:
commit
bddd51dd5d
14 changed files with 8441 additions and 0 deletions
4492
client/package-lock.json
generated
Normal file
4492
client/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
26
client/package.json
Normal file
26
client/package.json
Normal file
|
|
@ -0,0 +1,26 @@
|
|||
{
|
||||
"name": "graffiti-client",
|
||||
"version": "0.1.0",
|
||||
"main": "./build/index.js",
|
||||
"scripts": {
|
||||
"dev": "webpack-dev-server --mode development",
|
||||
"start": "npm run dev",
|
||||
"build": "webpack"
|
||||
},
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"@webpack-cli/serve": "^2.0.5",
|
||||
"html-webpack-plugin": "^5.6.0",
|
||||
"webpack": "^5.89.0",
|
||||
"webpack-cli": "^5.1.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@pixi/utils": "^7.3.2",
|
||||
"cors": "^2.8.5",
|
||||
"dat.gui": "^0.7.9",
|
||||
"pixi.js": "^7.3.2",
|
||||
"socket.io": "^4.7.2",
|
||||
"socket.io-client": "^4.7.2",
|
||||
"webpack-dev-server": "^4.15.1"
|
||||
}
|
||||
}
|
||||
215
client/src/App.js
Normal file
215
client/src/App.js
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
|
||||
import * as PIXI from 'pixi.js';
|
||||
import {io} from 'socket.io-client';
|
||||
|
||||
import {DrawingApp, Layer} from './DrawingApp';
|
||||
|
||||
const socket = io('ws://192.168.0.155:7777');
|
||||
|
||||
var id = "";
|
||||
var drawing_app = null;
|
||||
|
||||
var game_info = document.getElementById('game_state_info');
|
||||
var pixi_wrapper = document.getElementById('pixi-wrapper');
|
||||
|
||||
function setGameInfo(info) {
|
||||
game_info.innerHTML = info;
|
||||
}
|
||||
|
||||
socket.on('connect', () => {
|
||||
console.log("\n*** CONNECTED TO SOCKET *** ", socket.id);
|
||||
|
||||
id = socket.id;
|
||||
|
||||
console.log("\n*** Startup! ***");
|
||||
});
|
||||
|
||||
document.getElementById('join_room_btn').addEventListener("click", (e) => {
|
||||
var room_code = document.getElementById('room_code').value;
|
||||
var username = document.getElementById('username').value;
|
||||
|
||||
socket.emit('join_room', room_code, username);
|
||||
});
|
||||
|
||||
socket.on('room', (room_id) => {
|
||||
// join the room
|
||||
console.log(room_id);
|
||||
document.getElementById('game_room_code').innerHTML = room_id;
|
||||
|
||||
document.getElementById('game_setup').style.display = "none";
|
||||
document.getElementById('game_info').style.display = "initial";
|
||||
});
|
||||
|
||||
socket.on('room_error', (error_msg) => {
|
||||
alert(error_msg);
|
||||
});
|
||||
|
||||
document.getElementById('create_room_btn').addEventListener("click", (e) => {
|
||||
// Tell the server we want a new room
|
||||
var username = document.getElementById('username').value;
|
||||
socket.emit("create_room", username);
|
||||
});
|
||||
|
||||
socket.on('player_data', (player_arr) => {
|
||||
var count = player_arr.length;
|
||||
document.getElementById('current_player_count').innerHTML = count;
|
||||
|
||||
var player_list = document.getElementById('player_list');
|
||||
|
||||
player_list.innerHTML = "";
|
||||
|
||||
player_arr.forEach((player) => {
|
||||
player_list.innerHTML += "<li>" + player.player_name + "</li>";
|
||||
})
|
||||
|
||||
document.getElementById('start_game_btn').disabled = (count < 5);
|
||||
});
|
||||
|
||||
document.getElementById('start_game_btn').addEventListener('click', (e) => {
|
||||
socket.emit('start_game');
|
||||
});
|
||||
|
||||
socket.on('game_started', () => {
|
||||
// game_data.game_leader =
|
||||
console.log("yooo");
|
||||
document.getElementById('pre_game_player_list').style.display = "none";
|
||||
});
|
||||
|
||||
socket.on('leader_selected', (leader) => {
|
||||
console.log(leader);
|
||||
console.log(id);
|
||||
setGameInfo(`Please wait while the leader ${leader.player_name} is selecting a topic to be painted`);
|
||||
if(leader.id == id) {
|
||||
setGameInfo("You are the game leader! Please choose a topic and a category that the topic fits in.");
|
||||
document.getElementById('game_leader_input').style.display = "initial";
|
||||
|
||||
document.getElementById('submit_topic').addEventListener('click', (e) => {
|
||||
var topic = document.getElementById('game_topic').value;
|
||||
var category = document.getElementById('game_category').value;
|
||||
|
||||
socket.emit('topic_selected', topic, category);
|
||||
})
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('topic_selected', (topic, category) => {
|
||||
console.log(topic, category);
|
||||
document.getElementById('game_leader_input').style.display = "none";
|
||||
setGameInfo(`Topic: ${topic}<br />Category: ${category}`);
|
||||
});
|
||||
|
||||
socket.on('game_finished', (leader, player_list) => {
|
||||
var pl = document.getElementById("endgame_player_list");
|
||||
|
||||
pl.innerHTML = "";
|
||||
|
||||
player_list.forEach((player) => {
|
||||
if (player.id == leader.id) {
|
||||
pl.innerHTML += `<li style="font-weight: bold; color: black;">${player.player_name} (Leader)</li>`;
|
||||
}
|
||||
else {
|
||||
pl.innerHTML += `<li style="color: ${player.player_color};">${player.player_name}</li>`;
|
||||
}
|
||||
})
|
||||
|
||||
pl.style.display = "initial";
|
||||
|
||||
var ink_bar = document.getElementById('myBar');
|
||||
ink_bar.style.display = "none";
|
||||
|
||||
document.getElementById('current_user_text').style.display = "none";
|
||||
});
|
||||
|
||||
socket.on('actually_start_game', (player_data) => {
|
||||
// Get player with our id to get the correct color
|
||||
drawing_app = new DrawingApp(pixi_wrapper, player_data);
|
||||
window.drawing_app = drawing_app;
|
||||
});
|
||||
|
||||
socket.on('draw_data', (round_num, data) => {
|
||||
console.log("Draw Data: ", round_num);
|
||||
var cur_layer = drawing_app.layers[round_num];
|
||||
|
||||
cur_layer.drawPointLine(data[0], data[1], true);
|
||||
});
|
||||
|
||||
socket.on('advance_round', (round_num, current_player) => {
|
||||
var ink_bar = document.getElementById('myBar');
|
||||
ink_bar.style.width = "100%";
|
||||
|
||||
var current_user_text = `It's <span style="white-space:pre; font-weight: bold; color: ${current_player.player_color}">${current_player.player_name}</span>'s turn to draw!`
|
||||
|
||||
document.getElementById('current_user_text').innerHTML = current_user_text;
|
||||
|
||||
if(id == current_player.id) {
|
||||
ink_bar.style.backgroundColor = current_player.player_color;
|
||||
ink_bar.style.visibility = "visible";
|
||||
|
||||
const cur_layer = drawing_app.layers[round_num];
|
||||
|
||||
cur_layer.enable();
|
||||
|
||||
cur_layer.setLivePaintProgressCallback((old_pos, new_pos) => {
|
||||
var ink_pct = cur_layer.getPaintPercentage();
|
||||
|
||||
ink_bar.style.width = ((1.0 - ink_pct) * 100) + "%";
|
||||
|
||||
console.log(ink_pct);
|
||||
|
||||
socket.emit("draw_data", [old_pos, new_pos]);
|
||||
});
|
||||
|
||||
cur_layer.setFinishedPaintingCallback(() => {
|
||||
cur_layer.disable();
|
||||
socket.emit("round_finished");
|
||||
});
|
||||
|
||||
/*player_data.forEach((p) => {
|
||||
if(p.id == id) {
|
||||
ink_bar.style.backgroundColor = p.player_color;
|
||||
// can probably get the color from current_player
|
||||
// since we only show this if we are the current player
|
||||
// yeah...
|
||||
}
|
||||
});*/
|
||||
}
|
||||
else {
|
||||
ink_bar.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
// TODO: Set current drawing layer, activate if WE are drawing
|
||||
// If we are not drawing, still hook drawing data from server into
|
||||
// drawing app
|
||||
|
||||
// TODO: Figure out how to best "unhook" these afterwards
|
||||
});
|
||||
|
||||
/*function display_colors() {
|
||||
var color_selection = document.getElementById('color_selection');
|
||||
color_selection.innerHTML = "";
|
||||
|
||||
available_colors.forEach((element) => {
|
||||
let newDiv = document.createElement('div');
|
||||
newDiv.classList.add('color');
|
||||
newDiv.style.backgroundColor = element;
|
||||
|
||||
color_selection.appendChild(newDiv);
|
||||
});
|
||||
};*/
|
||||
|
||||
|
||||
|
||||
/*const drawing_app = new DrawingApp(pixi_wrapper, {
|
||||
'players': [
|
||||
{'player_id': 'A', 'color': '#c35a00'},
|
||||
{'player_id': 'B', 'color': '#290097'}
|
||||
]
|
||||
});*/
|
||||
|
||||
/*drawing_app.layers[0].setLivePaintProgressCallback((old_pos, new_pos) => {
|
||||
console.log(old_pos);
|
||||
socket.emit("draw_data", [old_pos, new_pos]);
|
||||
})*/
|
||||
|
||||
// Test
|
||||
|
||||
61
client/src/BrushGenerator.js
Normal file
61
client/src/BrushGenerator.js
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import * as PIXI from 'pixi.js';
|
||||
|
||||
const fragment = `
|
||||
uniform float size;
|
||||
uniform float erase;
|
||||
uniform vec3 color;
|
||||
uniform float smoothing;
|
||||
|
||||
void main(){
|
||||
vec2 uv = vec2(gl_FragCoord.xy) / size;
|
||||
float dst = distance(uv, vec2(0.5, 0.5)) * 2.;
|
||||
float alpha = max(0., 1. - dst);
|
||||
alpha = pow(alpha, smoothing);
|
||||
if(erase == 0.)
|
||||
gl_FragColor = vec4(color, 1) * alpha;
|
||||
else
|
||||
gl_FragColor = vec4(alpha);
|
||||
}
|
||||
`;
|
||||
|
||||
export default class BrushGenerator {
|
||||
constructor(renderer) {
|
||||
this.renderer = renderer;
|
||||
|
||||
this.filter = new PIXI.Filter(null, fragment, {
|
||||
color: [0, 0, 0],
|
||||
size: 16,
|
||||
erase: 0,
|
||||
smoothing: 0.01
|
||||
});
|
||||
}
|
||||
|
||||
get(size, color, smoothing) {
|
||||
this.filter.uniforms.size = size;
|
||||
this.filter.uniforms.color = color;
|
||||
this.filter.uniforms.smoothing = smoothing;
|
||||
|
||||
const texture = PIXI.RenderTexture.create({width: size, height: size});
|
||||
texture.baseTexture.premultipliedAlpha = true;
|
||||
const sprite = new PIXI.Sprite();
|
||||
sprite.width = size;
|
||||
sprite.height = size;
|
||||
|
||||
sprite.filters = [this.filter];
|
||||
|
||||
this.renderer.render(sprite, {
|
||||
renderTexture: texture,
|
||||
clear: false,
|
||||
});
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
hexToArray(color) {
|
||||
const r = color >> 16;
|
||||
const g = (color & 0x00ffff) >> 8;
|
||||
const b = color & 0x0000ff;
|
||||
|
||||
return [r / 255, g / 255, b / 255];
|
||||
}
|
||||
}
|
||||
202
client/src/DrawingApp.js
Normal file
202
client/src/DrawingApp.js
Normal file
|
|
@ -0,0 +1,202 @@
|
|||
import * as PIXI from 'pixi.js';
|
||||
|
||||
import BrushGenerator from './BrushGenerator';
|
||||
import SpritePool from './SpritePool';
|
||||
|
||||
const max_ink_per_layer = 1000;
|
||||
|
||||
const layer_width = 512;
|
||||
const layer_height = 512;
|
||||
|
||||
const brush_size = 16;
|
||||
const brush_smoothing = 0.01;
|
||||
|
||||
export class Layer {
|
||||
constructor(app, render_texture, brushGenerator, player_id, color) {
|
||||
this.app = app;
|
||||
this.render_texture = render_texture;
|
||||
this.spritePool = new SpritePool();
|
||||
|
||||
this.ink_used = 0.0;
|
||||
this.lifted = false;
|
||||
|
||||
this.player_id = player_id;
|
||||
this.color = this.hexToRgb(color);
|
||||
this.draw_buffer = new PIXI.Container();
|
||||
|
||||
this.sprite = new PIXI.Sprite(this.render_texture);
|
||||
this.sprite.width = layer_width;
|
||||
this.sprite.height = layer_height;
|
||||
this.sprite.eventMode = 'none';
|
||||
this.app.stage.addChildAt(this.sprite, 0);
|
||||
|
||||
this.brushTexture =
|
||||
brushGenerator.get(brush_size, this.color, brush_smoothing);
|
||||
|
||||
this.drawingStarted = false;
|
||||
this.lastPosition = null;
|
||||
|
||||
const onDown = (e) => {
|
||||
const position = this.sprite.toLocal(e.data.global);
|
||||
// position.x += 512;
|
||||
// position.y += 512;
|
||||
|
||||
this.lastPosition = position;
|
||||
this.drawingStarted = true;
|
||||
};
|
||||
|
||||
const onMove = (e) => {
|
||||
const position = this.sprite.toLocal(e.data.global);
|
||||
// position.x += 512;
|
||||
// position.y += 512;
|
||||
// console.log(position);
|
||||
|
||||
if (this.drawingStarted) {
|
||||
this.drawPointLine(this.lastPosition, position);
|
||||
}
|
||||
|
||||
this.lastPosition = position;
|
||||
};
|
||||
const onUp = (e) => {
|
||||
this.lifted = true;
|
||||
this.drawingStarted = false;
|
||||
this.finished_painting_cb();
|
||||
};
|
||||
|
||||
this.sprite.on('mousedown', onDown);
|
||||
this.sprite.on('touchstart', onDown);
|
||||
this.sprite.on('mousemove', onMove);
|
||||
this.sprite.on('touchmove', onMove);
|
||||
this.sprite.on('mouseup', onUp);
|
||||
this.sprite.on('touchend', onUp);
|
||||
|
||||
this.app.ticker.add(() => {
|
||||
this.renderPoints();
|
||||
});
|
||||
}
|
||||
|
||||
getPaintPercentage() {
|
||||
return Math.min(this.ink_used / max_ink_per_layer, 1.0);
|
||||
}
|
||||
|
||||
hexToRgb(hex) {
|
||||
var res = hex.match(/[a-f0-9]{2}/gi);
|
||||
return res && res.length === 3 ? res.map(function(v) {
|
||||
return parseInt(v, 16) / 255
|
||||
}) :
|
||||
[0, 0, 0];
|
||||
}
|
||||
|
||||
setFinishedPaintingCallback(callback) {
|
||||
this.finished_painting_cb = callback;
|
||||
}
|
||||
|
||||
setLivePaintProgressCallback(callback) {
|
||||
this.live_paint_progress_cb = callback;
|
||||
}
|
||||
|
||||
drawPoint(x, y) {
|
||||
const sprite = this.spritePool.get();
|
||||
sprite.x = x;
|
||||
sprite.y = y;
|
||||
sprite.texture = this.brushTexture;
|
||||
|
||||
sprite.blendMode = PIXI.BLEND_MODES.NORMAL;
|
||||
|
||||
this.draw_buffer.addChild(sprite);
|
||||
}
|
||||
|
||||
drawPointLine(oldPos, newPos, force = false) {
|
||||
if (this.lifted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const delta = {
|
||||
x: oldPos.x - newPos.x,
|
||||
y: oldPos.y - newPos.y,
|
||||
};
|
||||
const deltaLength = Math.sqrt(delta.x ** 2 + delta.y ** 2);
|
||||
|
||||
if (!force) {
|
||||
this.ink_used += deltaLength;
|
||||
this.live_paint_progress_cb(oldPos, newPos);
|
||||
}
|
||||
|
||||
if (this.ink_used >= max_ink_per_layer) {
|
||||
this.lifted = true;
|
||||
this.drawingStarted = false;
|
||||
this.finished_painting_cb();
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO: Pass socket.io to tell server?
|
||||
// Or save to array and just pass to server on round-end. Should be easier?
|
||||
|
||||
this.drawPoint(newPos.x, newPos.y);
|
||||
|
||||
if (deltaLength >= brush_size / 8) {
|
||||
const additionalPoints = Math.ceil(deltaLength / (brush_size / 8));
|
||||
|
||||
for (let i = 1; i < additionalPoints; i++) {
|
||||
const pos = {
|
||||
x: newPos.x + delta.x * (i / additionalPoints),
|
||||
y: newPos.y + delta.y * (i / additionalPoints),
|
||||
};
|
||||
|
||||
this.drawPoint(pos.x, pos.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
renderPoints() {
|
||||
this.app.renderer.render(this.draw_buffer, {
|
||||
renderTexture: this.render_texture,
|
||||
clear: false,
|
||||
});
|
||||
|
||||
this.draw_buffer.children = [];
|
||||
|
||||
this.spritePool.reset();
|
||||
}
|
||||
|
||||
enable() {
|
||||
this.sprite.eventMode = 'static';
|
||||
}
|
||||
|
||||
disable() {
|
||||
this.sprite.eventMode = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
export class DrawingApp {
|
||||
constructor(dom_elem, player_data) {
|
||||
var self = this;
|
||||
this.app = new PIXI.Application({
|
||||
width: layer_width,
|
||||
height: layer_height,
|
||||
backgroundColor: 0xffffff,
|
||||
});
|
||||
|
||||
this.brushGenerator = new BrushGenerator(this.app.renderer);
|
||||
this.brushTexture = null;
|
||||
|
||||
this.layers = [];
|
||||
|
||||
for (var i = 0; i < player_data.length * 2; i++) {
|
||||
const render_texture =
|
||||
PIXI.RenderTexture.create({width: 1024, height: 1024});
|
||||
var cur_player = player_data[i % player_data.length];
|
||||
this.layers.push(new Layer(
|
||||
this.app, render_texture, this.brushGenerator, cur_player.id,
|
||||
cur_player.player_color));
|
||||
}
|
||||
|
||||
dom_elem.appendChild(this.app.view);
|
||||
}
|
||||
|
||||
get() {
|
||||
return this.app;
|
||||
}
|
||||
|
||||
lock_all_layers() {}
|
||||
}
|
||||
29
client/src/SpritePool.js
Normal file
29
client/src/SpritePool.js
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
import * as PIXI from 'pixi.js';
|
||||
|
||||
export default class SpritePool {
|
||||
constructor() {
|
||||
this.sprites = [];
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
get() {
|
||||
if(this.index < this.sprites.length) {
|
||||
return this.sprites[this.index++];
|
||||
}
|
||||
|
||||
const sprite = new PIXI.Sprite(PIXI.Texture.EMPTY);
|
||||
sprite.anchor.set(0.5);
|
||||
this.sprites.push(sprite);
|
||||
|
||||
return sprite;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.index = 0;
|
||||
}
|
||||
|
||||
destroy() {
|
||||
for(let i = 0; i < this.sprites.length; i++)
|
||||
this.sprites[i].destroy();
|
||||
}
|
||||
}
|
||||
162
client/src/index.html
Normal file
162
client/src/index.html
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Fake Artist</title>
|
||||
<style>
|
||||
body {
|
||||
margin: 0 auto;
|
||||
background-color: black;
|
||||
font-size: 1.4vmax;
|
||||
}
|
||||
|
||||
input {
|
||||
font-size: 1.0vmax;
|
||||
}
|
||||
|
||||
#color_selection {
|
||||
display: grid;
|
||||
grid-template-columns: 25px 25px 25px 25px 25px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.gameinfo {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
li {
|
||||
color: #FFFFFF;
|
||||
}
|
||||
|
||||
.color {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.center {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
#game_leader_input {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#pixi-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
display: initial;
|
||||
}
|
||||
|
||||
#game_info {
|
||||
color: #FFFFFF;
|
||||
display: none;
|
||||
}
|
||||
|
||||
#myProgress {
|
||||
width: 100%;
|
||||
background-color: black;
|
||||
}
|
||||
|
||||
#myBar {
|
||||
width: 100%;
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
#pixi-wrapper {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
/*display: initial;*/
|
||||
}
|
||||
|
||||
#current_user_text {
|
||||
color: black;
|
||||
background-color: #FFFFFF;
|
||||
}
|
||||
|
||||
#endgame_player_list {
|
||||
display: none;
|
||||
color: black;
|
||||
background-color: white;
|
||||
list-style-type: none;
|
||||
}
|
||||
|
||||
#pl_container {
|
||||
background-color: white;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
label {
|
||||
color: white;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div class="center">
|
||||
<div id="game_setup">
|
||||
<form>
|
||||
<label>
|
||||
Username:
|
||||
<input id="username" type="text" placeholder="Username" maxlength="16" />
|
||||
</label>
|
||||
</form>
|
||||
<form>
|
||||
<label>
|
||||
Room-Code: <input id="room_code" maxlength="4" type="text" placeholder="Room-Code" />
|
||||
</label>
|
||||
<input id="join_room_btn" type="button" value="Join Room" />
|
||||
</form>
|
||||
<form>
|
||||
<input id="create_room_btn" type="button" value="Create Room" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
<div class="center">
|
||||
<div id="game_info">
|
||||
<span id="game_room_code"></span>
|
||||
<div id="pre_game_player_list" class="gameinfo">
|
||||
<span id="current_player_count">X</span>/<span id="max_player_count">10</span>
|
||||
<ul id="player_list">
|
||||
</ul>
|
||||
<form>
|
||||
<input id="start_game_btn" type="button" value="Start" />
|
||||
</form>
|
||||
</div>
|
||||
<div>
|
||||
<span id="game_state_info"></span>
|
||||
</div>
|
||||
<div id="game_leader_input">
|
||||
<form>
|
||||
<label>
|
||||
Topic: <input type="text" placeholder="Topic" id="game_topic" />
|
||||
</label>
|
||||
<label>
|
||||
Category: <input type="text" placeholder="Category" id="game_category" />
|
||||
</label>
|
||||
<input type="button" id="submit_topic" value="Start" />
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="game">
|
||||
<div class="center" id="pl_container">
|
||||
<ul id="endgame_player_list"></ul>
|
||||
</div>
|
||||
<div id="user_turn_info">
|
||||
<div class="center" id="current_user_text"></div>
|
||||
<div>
|
||||
<div class="center" id="myProgress">
|
||||
<div id="myBar"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="pixi-wrapper"></div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
372
client/src/server/server.js
Normal file
372
client/src/server/server.js
Normal file
|
|
@ -0,0 +1,372 @@
|
|||
const express = require('express');
|
||||
const app = express();
|
||||
const http = require('http').Server(app);
|
||||
const io = require('socket.io')(http);
|
||||
const cors = require('cors');
|
||||
|
||||
const room_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
||||
|
||||
Array.prototype.remove = function() {
|
||||
var what, a = arguments, L = a.length, ax;
|
||||
while (L && this.length) {
|
||||
what = a[--L];
|
||||
while ((ax = this.indexOf(what)) !== -1) {
|
||||
this.splice(ax, 1);
|
||||
}
|
||||
}
|
||||
return this;
|
||||
};
|
||||
|
||||
function randomInt(low, high) {
|
||||
return Math.floor(Math.random() * (high - low + 1) + low);
|
||||
}
|
||||
|
||||
function randomId() {
|
||||
let out = '';
|
||||
for (let i = 0; i < 4; i++) {
|
||||
out += room_chars[randomInt(0, room_chars.length - 1)];
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
const available_colors = [
|
||||
'#c35a00', '#290097', '#b8c400', '#8c8eff', '#47d058', '#cf0036', '#01cefd',
|
||||
'#500027', '#006241', '#ffc4e4'
|
||||
];
|
||||
|
||||
class Player {
|
||||
id = null;
|
||||
player_name = 'Player';
|
||||
player_color = '';
|
||||
|
||||
constructor(id_, name_, player_color_) {
|
||||
this.id = id_;
|
||||
this.player_name = name_;
|
||||
this.player_color = player_color_;
|
||||
}
|
||||
}
|
||||
|
||||
const GAME_STATE = {
|
||||
PRE_GAME: 0,
|
||||
TOPIC_SELECTION: 1,
|
||||
DRAWING: 2,
|
||||
VOTING: 3,
|
||||
RESULTS: 4
|
||||
}
|
||||
|
||||
class Game {
|
||||
room_id = '';
|
||||
topic = '';
|
||||
category = '';
|
||||
players = []; // Player Class
|
||||
actual_players = [];
|
||||
current_game_state = 0;
|
||||
current_player = null;
|
||||
leader = null;
|
||||
fake_artist = null;
|
||||
current_round = 0;
|
||||
player_turns = [];
|
||||
disconnected_during_session = [];
|
||||
|
||||
constructor(room_id_) {
|
||||
this.room_id = room_id_
|
||||
}
|
||||
|
||||
add_player(player) {
|
||||
this.players.push(player);
|
||||
}
|
||||
|
||||
remove_player(player_id) {
|
||||
this.players.splice(
|
||||
this.players.findIndex(item => item.id === player_id), 1)
|
||||
}
|
||||
}
|
||||
|
||||
var active_games = {};
|
||||
|
||||
/*
|
||||
GameLoop:
|
||||
- Lobby
|
||||
- Wait for 5+ Players -> enable "start" button (any player can start the
|
||||
game)
|
||||
- Randomly select one Player to be "game leader"
|
||||
- All other players have to draw
|
||||
- They take turns, only getting to see the image once its their turn (may be
|
||||
changed, depending) on how boring it gets to wait (maybe only let game leade
|
||||
peek)
|
||||
- Once everybody painted two strokes (two full rounds of the game)
|
||||
Reveal the image to everybody
|
||||
*/
|
||||
|
||||
initGame =
|
||||
() => {
|
||||
console.log('Initializing Game!');
|
||||
let count = 0;
|
||||
// Game.map = {};
|
||||
// console.log("Map Created: ", Game.map);
|
||||
}
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.static('static'));
|
||||
|
||||
app.get('/', (req, res) => {
|
||||
res.sendFile('index.html');
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
console.log('User: ', socket.id, ' connected.');
|
||||
|
||||
socket.on('draw_data', (arr) => {
|
||||
if (socket.room) {
|
||||
var room_id = socket.room;
|
||||
var game = active_games[room_id];
|
||||
|
||||
socket.broadcast.to(game.room_id)
|
||||
.emit('draw_data', game.current_round - 1, arr);
|
||||
}
|
||||
|
||||
console.log(arr[0], arr[1]);
|
||||
});
|
||||
|
||||
// socket.on('')
|
||||
|
||||
socket.on('create_room', (username) => {
|
||||
if (username == '') {
|
||||
socket.emit('room_error', 'Invalid username!');
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('New room');
|
||||
var room_id = randomId();
|
||||
console.log(room_id);
|
||||
socket.join(room_id);
|
||||
socket.emit('room', room_id);
|
||||
console.log(io.sockets.adapter.rooms);
|
||||
|
||||
var game = new Game(room_id);
|
||||
|
||||
var player_color = available_colors[0];
|
||||
|
||||
for (var i = 0; i < available_colors.length; i++) {
|
||||
var free = true;
|
||||
for (var k = 0; k < game.players.length; k++) {
|
||||
if (available_colors[i] == game.players[k].player_color) {
|
||||
free = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (free) {
|
||||
player_color = available_colors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var player = new Player(socket.id, username, player_color);
|
||||
|
||||
game.add_player(player);
|
||||
|
||||
socket.room = room_id;
|
||||
|
||||
active_games[room_id] = game;
|
||||
io.to(room_id).emit('player_data', active_games[room_id].players);
|
||||
console.log(JSON.stringify(active_games[room_id]));
|
||||
});
|
||||
|
||||
socket.on('start_game', () => {
|
||||
if (socket.room) {
|
||||
var room_id = socket.room;
|
||||
var game = active_games[room_id];
|
||||
|
||||
game.game_state = GAME_STATE.TOPIC_SELECTION;
|
||||
|
||||
io.to(room_id).emit('game_started');
|
||||
|
||||
var leader =
|
||||
game.players[Math.floor(Math.random() * game.players.length)];
|
||||
game.leader = leader;
|
||||
|
||||
var remaining_players = game.players.filter(e => e !== leader);
|
||||
game.actual_players = remaining_players;
|
||||
game.player_turns = game.actual_players.concat(game.actual_players);
|
||||
|
||||
var fake_artist = remaining_players[Math.floor(
|
||||
Math.random() * remaining_players.length)];
|
||||
|
||||
game.fake_artist = fake_artist;
|
||||
|
||||
io.to(room_id).emit('leader_selected', leader);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('topic_selected', (topic, category) => {
|
||||
console.log(topic, category);
|
||||
|
||||
if (socket.room) {
|
||||
var room_id = socket.room;
|
||||
var game = active_games[room_id];
|
||||
|
||||
game.topic = topic;
|
||||
game.category = category;
|
||||
|
||||
game.players.forEach((player) => {
|
||||
console.log(player.id);
|
||||
if (player.id != game.fake_artist.id) {
|
||||
console.log('OK');
|
||||
io.to(player.id).emit('topic_selected', topic, category);
|
||||
} else {
|
||||
console.log('FAKE');
|
||||
io.to(player.id).emit('topic_selected', '???', category);
|
||||
}
|
||||
});
|
||||
|
||||
game.game_state = GAME_STATE.DRAWING;
|
||||
io.to(socket.room).emit('actually_start_game', game.actual_players);
|
||||
|
||||
|
||||
// FIXME: Super slow for some reason
|
||||
// Maybe just do the following:
|
||||
// Have a manual advance on the clients (button, ink empty, on mouse up)
|
||||
// If someone disconnects, just manually advance, no timeout needed
|
||||
game.current_round = 0;
|
||||
|
||||
// game.current_player = game.player_turns[game.current_round];
|
||||
// socket.to(socket.room).emit('advance_round', game.current_round,
|
||||
// game.current_player);
|
||||
|
||||
play_next_turn(game);
|
||||
}
|
||||
});
|
||||
|
||||
function play_next_turn(game) {
|
||||
console.log('Round: ', game.current_round);
|
||||
|
||||
if (game.game_state == GAME_STATE.VOTING) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (game.current_round >= game.player_turns.length) {
|
||||
game.game_state = GAME_STATE.VOTING;
|
||||
console.log('GAME FINISHED!');
|
||||
io.to(game.room_id).emit('game_finished', game.leader, game.players);
|
||||
game.game_state = GAME_STATE.VOTING;
|
||||
// TODO: Pass fake artist after voting... implement voting in the first
|
||||
// place
|
||||
return;
|
||||
}
|
||||
|
||||
//while(game.current_player)
|
||||
|
||||
game.current_player = game.player_turns[game.current_round];
|
||||
io.to(game.room_id)
|
||||
.emit('advance_round', game.current_round, game.current_player);
|
||||
|
||||
game.current_round++;
|
||||
}
|
||||
|
||||
socket.on('round_finished', () => {
|
||||
if (socket.room) {
|
||||
var room_id = socket.room;
|
||||
var game = active_games[room_id];
|
||||
|
||||
if (game.game_state != GAME_STATE.DRAWING) {
|
||||
return;
|
||||
}
|
||||
|
||||
console.log('round finished!');
|
||||
play_next_turn(game);
|
||||
}
|
||||
});
|
||||
|
||||
// socket.
|
||||
|
||||
socket.on('join_room', (room_id, username) => {
|
||||
if (username == '') {
|
||||
socket.emit('room_error', 'Invalid username!');
|
||||
return;
|
||||
}
|
||||
|
||||
if (!active_games[room_id]) {
|
||||
socket.emit('room_error', 'This room does not exist!');
|
||||
return;
|
||||
}
|
||||
|
||||
var user_count = active_games[room_id].players.length;
|
||||
|
||||
if (user_count > 10) {
|
||||
socket.emit('room_error', 'This room is full!');
|
||||
return;
|
||||
}
|
||||
|
||||
var game_state = active_games[room_id].current_game_state;
|
||||
|
||||
if (game_state != GAME_STATE.PRE_GAME) {
|
||||
socket.emit('room_error', 'This game is already in progress!');
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
var game = active_games[room_id];
|
||||
|
||||
socket.join(room_id);
|
||||
|
||||
var player_color = available_colors[0];
|
||||
|
||||
for (var i = 0; i < available_colors.length; i++) {
|
||||
var free = true;
|
||||
for (var k = 0; k < game.players.length; k++) {
|
||||
if (available_colors[i] == game.players[k].player_color) {
|
||||
free = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (free) {
|
||||
player_color = available_colors[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var player = new Player(socket.id, username, player_color);
|
||||
active_games[room_id].add_player(player);
|
||||
|
||||
socket.room = room_id;
|
||||
|
||||
socket.emit('room', room_id);
|
||||
io.to(room_id).emit('player_data', active_games[room_id].players);
|
||||
console.log(JSON.stringify(active_games[room_id]));
|
||||
});
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
console.log('User: ', socket.id, ' disconnected.');
|
||||
|
||||
if (socket.room) {
|
||||
var room_id = socket.room;
|
||||
var game = active_games[room_id];
|
||||
game.remove_player(room_id);
|
||||
io.to(room_id).emit('player_data', game.players);
|
||||
|
||||
console.log(socket.id, game.current_player);
|
||||
|
||||
if (game.game_state == GAME_STATE.DRAWING) {
|
||||
game.disconnected_during_session.push(game.players[socket.id]);
|
||||
|
||||
/*console.log(game.player_turns);
|
||||
game.player_turns = game.player_turns.filter(e => e.id != socket.id);
|
||||
console.log(game.player_turns);*/
|
||||
}
|
||||
|
||||
|
||||
if (socket.id == game.current_player.id) {
|
||||
play_next_turn(game);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
http.listen(7777, '0.0.0.0', () => {
|
||||
console.log('Listening on 7777');
|
||||
})
|
||||
|
||||
initGame();
|
||||
21
client/webpack.config.js
Normal file
21
client/webpack.config.js
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
const path = require('path');
|
||||
const HtmlWebpackPlugin = require('html-webpack-plugin');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/App.js',
|
||||
output: {
|
||||
filename: './index.js',
|
||||
path: path.resolve(__dirname, 'build')
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
filename: 'index.html',
|
||||
template: path.resolve(__dirname, './src/index.html'),
|
||||
}),
|
||||
],
|
||||
devServer: {
|
||||
static: path.join(__dirname, 'build'),
|
||||
compress: true,
|
||||
port: 8080
|
||||
}
|
||||
};
|
||||
2801
client/yarn.lock
Normal file
2801
client/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue