This commit is contained in:
Sch1nken 2023-12-26 23:33:11 +01:00
commit bddd51dd5d
14 changed files with 8441 additions and 0 deletions

22
.gitignore vendored Normal file
View file

@ -0,0 +1,22 @@
# dependencies
node_modules
.pnp
.pnp.js
# testing
coverage
# production
build
# misc
.DS_Store
.env.local
.env.development.local
.env.test.local
.env.production.local
.idea
npm-debug.log*
yarn-debug.log*
yarn-error.log*

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020 OneWayBlock
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE
OR OTHER DEALINGS IN THE SOFTWARE.

11
README.md Normal file
View file

@ -0,0 +1,11 @@
# Clash of Coins - Graffiti Tutorial
This repository contains source code of Graffiti Tutorial Series. It consists of two main parts - `client` and `server`.
## Client
Client implements a rendering system which listens to mouse events or events emitted by the server and draws lines on the canvas. It supports variable brush size and color, and also has eraser mode.
1. Run `npm install` to install dependencies
2. Run `npm start` to launch a dev server
3. Open <http://localhost:8080>

4492
client/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

26
client/package.json Normal file
View 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
View 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&nbsp;<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

View 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
View 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
View 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
View 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
View 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
View 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

File diff suppressed because it is too large Load diff

6
package-lock.json generated Normal file
View file

@ -0,0 +1,6 @@
{
"name": "pixi-graffiti",
"lockfileVersion": 3,
"requires": true,
"packages": {}
}