Full game loop. TODO: Livejoin checks

This commit is contained in:
Sch1nken 2024-01-13 02:27:41 +01:00
parent ecb430e1b7
commit b7ad52ff6a
4 changed files with 709 additions and 346 deletions

View file

@ -9,8 +9,38 @@ const socket = io('http://localhost:7777');
var id = ''; var id = '';
var drawing_app = null; var drawing_app = null;
var game_info = document.getElementById('game_state_info'); var did_vote = false;
var pixi_wrapper = document.getElementById('pixi-wrapper');
const game_info = document.getElementById('game_state_info');
const pixi_wrapper = document.getElementById('pixi-wrapper');
const join_room_btn = document.getElementById('join_room_btn');
const room_code_input = document.getElementById('room_code');
const username_input = document.getElementById('username');
const game_room_code_label = document.getElementById('game_room_code');
const game_setup_panel = document.getElementById('game_setup');
const game_info_panel = document.getElementById('game_info');
const copy_image_btn = document.getElementById('copy_image');
const download_image_btn = document.getElementById('download_image');
const current_player_count = document.getElementById('current_player_count');
const player_list = document.getElementById('player_list');
const pre_game_player_list = document.getElementById('pre_game_player_list');
const ready_up_btn = document.getElementById('ready_up_btn');
const create_room_btn = document.getElementById('create_room_btn');
const game_leader_input = document.getElementById('game_leader_input');
const game_hint = document.getElementById('game_hint');
const game_topic = document.getElementById('game_topic');
const submit_topic_btn = document.getElementById('submit_topic');
const voting_player_list = document.getElementById('voting_player_list');
function setGameInfo(info) { function setGameInfo(info) {
game_info.innerHTML = info; game_info.innerHTML = info;
@ -24,87 +54,75 @@ socket.on('connect', () => {
console.log('\n*** Startup! ***'); console.log('\n*** Startup! ***');
}); });
document.getElementById('join_room_btn').addEventListener('click', (e) => { join_room_btn.addEventListener('click', (e) => {
var room_code = document.getElementById('room_code').value; socket.emit('join_room', room_code_input.value, username_input.value);
var username = document.getElementById('username').value;
socket.emit('join_room', room_code, username);
}); });
socket.on('room', (room_id) => { socket.on('join_room', (room_id) => {
// join the room // join the room
console.log(room_id); console.log(room_id);
document.getElementById('game_room_code').innerHTML = room_id; game_room_code_label.innerHTML = room_id;
document.getElementById('game_setup').style.display = 'none'; game_setup_panel.style.display = 'none';
document.getElementById('game_info').style.display = 'initial'; game_info_panel.style.display = 'initial';
document.getElementById('copy_image').style.display = 'none'; copy_image_btn.style.display = 'none';
document.getElementById('download_image').style.display = 'none'; download_image_btn.style.display = 'none';
}); });
socket.on('room_error', (error_msg) => { socket.on('room_error', (error_msg) => {
alert(error_msg); alert(error_msg);
}); });
document.getElementById('create_room_btn').addEventListener('click', (e) => { socket.on('topic_error', (error_msg) => {
alert(error_msg);
});
create_room_btn.addEventListener('click', (e) => {
// Tell the server we want a new room // Tell the server we want a new room
var username = document.getElementById('username').value; socket.emit('create_room', username_input.value);
socket.emit('create_room', username);
}); });
socket.on('player_data', (player_arr) => { socket.on('player_data', (player_arr) => {
console.log('Playerdata!?');
var count = player_arr.length; var count = player_arr.length;
document.getElementById('current_player_count').innerHTML = count; current_player_count.innerHTML = count;
var player_list = document.getElementById('player_list');
player_list.innerHTML = ''; player_list.innerHTML = '';
player_arr.forEach((player) => { player_arr.forEach((player) => {
player_list.innerHTML += '<li>' + player.player_name + '</li>'; player_list.innerHTML +=
`<li><input class="player_ready_state" type="checkbox" ${
player.ready ? 'checked' :
''} disabled />${player.player_name}</li>`;
}) })
document.getElementById('start_game_btn').disabled = (count < 2); ready_up_btn.disabled = (count < 2);
}); });
document.getElementById('start_game_btn').addEventListener('click', (e) => { ready_up_btn.addEventListener('click', (e) => {
socket.emit('start_game'); socket.emit('ready');
}); });
socket.on('game_started', () => { socket.on('game_started', () => {
// game_data.game_leader = voting_player_list.innerHTML = "";
console.log('yooo'); document.getElementById('pl_container').style.display = "none";
document.getElementById('pre_game_player_list').style.display = 'none'; document.getElementById('copy_image').style.display = 'none';
document.getElementById('download_image').style.display = 'none';
pixi_wrapper.innerHTML = "";
drawing_app = null;
pre_game_player_list.style.display = 'none';
}); });
socket.on('leader_selected', (leader) => { socket.on('leader_selected', (leader) => {
console.log(leader);
console.log(id);
setGameInfo(`Please wait while the leader ${ setGameInfo(`Please wait while the leader ${
leader.player_name} is selecting a hint to be painted`); leader.player_name} is thinking of a topic to be painted`);
if (leader.id == id) { if (leader.id == id) {
setGameInfo( setGameInfo(
'You are the game leader! Please choose a hint and a category that the hint fits in.'); 'You are the game leader! Please choose a hint and a category that the hint fits in.');
document.getElementById('game_leader_input').style.display = 'initial'; game_leader_input.style.display = 'initial';
const game_hint = document.getElementById('game_hint');
const game_topic = document.getElementById('game_topic');
const submit_topic_btn = document.getElementById('submit_topic');
function updateSubmitButtonState() {
submit_topic_btn.disabled =
game_hint.value.length < 2 || game_topic.value.length < 2;
}
game_hint.addEventListener('input', (e) => {
updateSubmitButtonState();
});
game_topic.addEventListener('input', (e) => {
updateSubmitButtonState();
});
submit_topic_btn.addEventListener('click', (e) => { submit_topic_btn.addEventListener('click', (e) => {
socket.emit('topic_selected', game_topic.value, game_hint.value); socket.emit('topic_selected', game_topic.value, game_hint.value);
@ -112,28 +130,118 @@ socket.on('leader_selected', (leader) => {
} }
}); });
function updateSubmitButtonState() {
submit_topic_btn.disabled =
game_hint.value.length < 2 || game_topic.value.length < 2;
}
game_hint.addEventListener('input', (e) => {
updateSubmitButtonState();
});
game_topic.addEventListener('input', (e) => {
updateSubmitButtonState();
});
socket.on('topic_selected', (topic, hint) => { socket.on('topic_selected', (topic, hint) => {
console.log(topic, hint); console.log(topic, hint);
document.getElementById('game_leader_input').style.display = 'none'; game_leader_input.style.display = 'none';
setGameInfo(`Hint: ${hint}<br />Topic: ${topic}`); setGameInfo(`Hint: ${hint}<br />Topic: ${topic}`);
}); });
socket.on('game_finished', (leader, player_list) => { socket.on('game_finished', (leader, player_list) => {
var pl = document.getElementById('endgame_player_list'); did_vote = false;
drawing_app.addBackgroundForDownload();
pl.innerHTML = ''; document.getElementById('pl_container').style.display = 'flex';
document.getElementById('copy_image').style.display = 'initial';
document.getElementById('download_image').style.display = 'initial';
document.getElementById('current_user_text').style.display = 'none';
player_list.forEach((player) => { var idx = 1;
if (player.id == leader.id) {
pl.innerHTML += `<li style="font-weight: bold; color: black;">${ player_list.forEach((p) => {
player.player_name} (Leader)</li>`; var tr = voting_player_list.insertRow(-1);
} else {
pl.innerHTML += `<li style="color: ${player.player_color};">${ var c1 = tr.insertCell(-1);
player.player_name}</li>`; c1.innerHTML = `<span id="votecount_${p.id}">?</span>`;
}
var c2 = tr.insertCell(-1);
// leader should not be able to vote
// spectators should not be able to vote
// basically, only show voting button if this players' id is in the list of
// voteable players
if (player_list.filter(e => e.id === id).length > 0) {
var vote_btn = document.createElement('input');
vote_btn.type = 'button';
vote_btn.value = 'vote';
vote_btn.classList.add('vote_btn');
vote_btn.addEventListener('click', () => {
console.log(p.id);
socket.emit('vote_for_player', p.id);
Array.from(document.getElementsByClassName('vote_btn'))
.forEach((element) => {
element.disabled = true;
element.style.display = 'none';
}) })
});
c2.appendChild(vote_btn);
}
pl.style.display = 'initial'; var c3 = tr.insertCell(-1);
c3.innerHTML = `<span id="playername_${p.id}" style="font-weight: bold; color: ${p.player_color}">${
p.player_name}</span>`;
var c4 = tr.insertCell(-1);
c4.innerHTML = `<span style="font-weight: bold; color: ${p.player_color}">${
idx}, ${idx + player_list.length}</span>`;
var c5 = tr.insertCell(-1);
var chkbox = document.createElement('input');
chkbox.type = 'checkbox';
chkbox.disabled = true;
chkbox.id = `votestatus_${p.id}`;
c5.appendChild(chkbox);
// c5.innerHTML = `<input type="checkbox" disabled />`;
idx++;
});
socket.on('show_vote_results', (votes) => {
votes.players_voted.forEach((pv) => {
var e = document.getElementById(`votestatus_${pv}`);
if (e) {
e.checked = true;
}
Object.entries(votes.player_votes).forEach(([k, v]) => {
var e = document.getElementById(`votecount_${k}`);
if (e) {
e.innerHTML = v;
}
});
var fake = document.getElementById(`playername_${votes.fake_artist.id}`);
if(fake) {
fake.innerHTML = `>>> ${votes.fake_artist.player_name} <<<`;
}
});
pre_game_player_list.style.display = "initial";
});
socket.on('update_votes', (votes) => {
// update player vote display
votes.players_voted.forEach((pv) => {
var e = document.getElementById(`votestatus_${pv}`);
if (e) {
e.checked = true;
}
});
});
var ink_bar = document.getElementById('myBar'); var ink_bar = document.getElementById('myBar');
ink_bar.style.display = 'none'; ink_bar.style.display = 'none';
@ -186,11 +294,8 @@ socket.on('game_finished', (leader, player_list) => {
base64_image.then((str) => { base64_image.then((str) => {
try { try {
navigator.clipboard.write([ navigator.clipboard.write(
new ClipboardItem({ [new ClipboardItem({'image/png': b64toBlob(str)})]);
'image/png': b64toBlob(str)
})
]);
} catch (error) { } catch (error) {
console.error(error); console.error(error);
} }
@ -200,18 +305,21 @@ socket.on('game_finished', (leader, player_list) => {
socket.on('actually_start_game', (player_data) => { socket.on('actually_start_game', (player_data) => {
// Get player with our id to get the correct color // Get player with our id to get the correct color
document.getElementById('current_user_text').style.display = 'flex';
var ink_bar = document.getElementById('myBar');
ink_bar.style.display = 'initial';
drawing_app = new DrawingApp(pixi_wrapper, player_data); drawing_app = new DrawingApp(pixi_wrapper, player_data);
window.drawing_app = drawing_app; window.drawing_app = drawing_app;
}); });
socket.on('draw_data', (round_num, data) => { socket.on('draw_data', (turn_num, data) => {
console.log('Draw Data: ', round_num); console.log('Draw Data: ', turn_num);
var cur_layer = drawing_app.layers[round_num]; var cur_layer = drawing_app.layers[turn_num];
cur_layer.drawPointLine(data[0], data[1], true); cur_layer.drawPointLine(data[0], data[1], true);
}); });
socket.on('advance_round', (round_num, current_player) => { socket.on('advance_turn', (turn_num, current_player) => {
var ink_bar = document.getElementById('myBar'); var ink_bar = document.getElementById('myBar');
ink_bar.style.width = '100%'; ink_bar.style.width = '100%';
@ -226,7 +334,7 @@ socket.on('advance_round', (round_num, current_player) => {
ink_bar.style.backgroundColor = current_player.player_color; ink_bar.style.backgroundColor = current_player.player_color;
ink_bar.style.visibility = 'visible'; ink_bar.style.visibility = 'visible';
const cur_layer = drawing_app.layers[round_num]; const cur_layer = drawing_app.layers[turn_num];
cur_layer.enable(); cur_layer.enable();
@ -235,14 +343,14 @@ socket.on('advance_round', (round_num, current_player) => {
ink_bar.style.width = ((1.0 - ink_pct) * 100) + '%'; ink_bar.style.width = ((1.0 - ink_pct) * 100) + '%';
console.log(ink_pct); console.log(ink_pct, old_pos, new_pos);
socket.emit('draw_data', [old_pos, new_pos]); socket.emit('draw_data', [old_pos, new_pos]);
}); });
cur_layer.setFinishedPaintingCallback(() => { cur_layer.setFinishedPaintingCallback(() => {
cur_layer.disable(); cur_layer.disable();
socket.emit('round_finished'); socket.emit('turn_finished');
}); });
/*player_data.forEach((p) => { /*player_data.forEach((p) => {

View file

@ -116,22 +116,22 @@ export class Layer {
y: oldPos.y - newPos.y, y: oldPos.y - newPos.y,
}; };
const deltaLength = Math.sqrt(delta.x ** 2 + delta.y ** 2); const deltaLength = Math.sqrt(delta.x ** 2 + delta.y ** 2);
this.ink_used += deltaLength;
if (!force) { if (!force) {
this.ink_used += deltaLength; console.log("Force?");
this.live_paint_progress_cb(oldPos, newPos); this.live_paint_progress_cb(oldPos, newPos);
} }
if (this.ink_used >= max_ink_per_layer) { if (this.ink_used >= max_ink_per_layer) {
this.lifted = true; this.lifted = true;
this.drawingStarted = false; this.drawingStarted = false;
if(!force) {
this.finished_painting_cb(); this.finished_painting_cb();
}
return; 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); this.drawPoint(newPos.x, newPos.y);
if (deltaLength >= brush_size / 8) { if (deltaLength >= brush_size / 8) {
@ -170,7 +170,6 @@ export class Layer {
export class DrawingApp { export class DrawingApp {
constructor(dom_elem, player_data) { constructor(dom_elem, player_data) {
var self = this;
this.app = new PIXI.Application({ this.app = new PIXI.Application({
width: layer_width, width: layer_width,
height: layer_height, height: layer_height,
@ -182,13 +181,6 @@ export class DrawingApp {
this.layers = []; this.layers = [];
const base_render_texture = PIXI.RenderTexture.create({width: 512, height: 512});
/*this.layers.push(new Layer(
this.app, base_render_texture, this.brushGenerator, "-1",
"0xFFFFFF"));*/
for (var i = 0; i < player_data.length * 2; i++) { for (var i = 0; i < player_data.length * 2; i++) {
const render_texture = const render_texture =
PIXI.RenderTexture.create({width: 512, height: 512}); PIXI.RenderTexture.create({width: 512, height: 512});
@ -205,5 +197,19 @@ export class DrawingApp {
return this.app; return this.app;
} }
addBackgroundForDownload() {
if(!this.hasBackgroundAlready) {
let rectangle = new PIXI.Graphics();
rectangle.lineStyle(0.5, 0x999999);
rectangle.beginFill(0xFFFFFF); // draw each row of rectangles in different color :)
rectangle.drawRect(0, 0, 512, 512);
rectangle.endFill();
this.app.stage.addChildAt(rectangle, 0);
this.hasBackgroundAlready = true;
}
}
lock_all_layers() {} lock_all_layers() {}
} }

View file

@ -25,6 +25,10 @@
color: #FFFFFF; color: #FFFFFF;
} }
.room_input {
text-transform: uppercase;
}
li { li {
color: #FFFFFF; color: #FFFFFF;
} }
@ -40,6 +44,11 @@
justify-content: center; justify-content: center;
} }
.table-center {
height: 100%;
justify-content: center;
}
#game_leader_input { #game_leader_input {
display: none; display: none;
} }
@ -88,15 +97,59 @@
#pl_container { #pl_container {
background-color: white; background-color: white;
margin-bottom: 20px; margin-bottom: 20px;
display: none;
} }
label { label {
color: white; color: white;
} }
#copy_image {} #copy_image {
display: none;
}
#download_image {} #download_image {
display: none;
}
#player_list {
list-style-type: none;
}
.player_ready_state {
width: 1.2vmax;
height: 1.2vmax;
}
#ready_up_btn {
color: black;
}
#ready_up_btn:disabled {
color: white;
}
.additional_info {
font-size: 1.5vmax;
color: white;
margin-top: 50px;
}
.additional_info>a:link {
color: red;
}
.additional_info>a:hover {
color: green;
}
.additional_info>a:active {
color: teal;
}
.additional_info>a:visited {
color: yellow;
}
</style> </style>
</head> </head>
@ -111,7 +164,8 @@
</form> </form>
<form> <form>
<label> <label>
Room-Code: <input id="room_code" maxlength="4" type="text" placeholder="Room-Code" /> Room-Code: <input class="room_input" id="room_code" maxlength="4" type="text"
placeholder="Room-Code" />
</label> </label>
<input id="join_room_btn" type="button" value="Join Room" /> <input id="join_room_btn" type="button" value="Join Room" />
</form> </form>
@ -128,7 +182,7 @@
<ul id="player_list"> <ul id="player_list">
</ul> </ul>
<form> <form>
<input id="start_game_btn" type="button" value="Start" /> <input id="ready_up_btn" type="button" value="Ready" />
</form> </form>
</div> </div>
<div> <div>
@ -137,18 +191,34 @@
<div id="game_leader_input"> <div id="game_leader_input">
<form> <form>
<label> <label>
Category: <input type="text" placeholder="Hint" id="game_hint" /> Hint (Everbody sees this!): <input type="text" placeholder="Hint" id="game_hint" />
</label> </label>
<br />
<label> <label>
Topic: <input type="text" placeholder="Topic" id="game_topic" /> Topic (Fake artist does NOT see this!): <input type="text" placeholder="Topic"
id="game_topic" />
</label> </label>
<input type="button" id="submit_topic" value="Start" disabled/> <input type="button" id="submit_topic" value="Start" disabled />
</form> </form>
</div> </div>
</div> </div>
</div> </div>
<div id="game"> <div id="game">
<div class="center" id="pl_container"> <div class="center" id="pl_container">
<table class="table-center">
<thead>
<tr>
<th>Votes</th>
<th>Vote?</th>
<th>Name</th>
<th>Turns</th>
<th>Has Voted?</th>
</tr>
</thead>
<tbody id="voting_player_list">
</tbody>
</table>
<ul id="endgame_player_list"></ul> <ul id="endgame_player_list"></ul>
</div> </div>
<div id="user_turn_info"> <div id="user_turn_info">
@ -161,10 +231,20 @@
</div> </div>
<div id="pixi-wrapper"></div> <div id="pixi-wrapper"></div>
<div class="center"> <div class="center">
<input type="button" id="copy_image" value="Save Image to Clipboard" /> <input type="button" id="copy_image" value="Copy to Clipboard (not working in Firefox)" />
<input type="button" id="download_image" value="Download Image" /> <input type="button" id="download_image" value="Download Image" />
</div> </div>
</div> </div>
<div class="additional_info" id="original_game_info">
This website is a (shitty) implementation of the board game <a
href="https://oinkgames.com/en/games/analog/a-fake-artist-goes-to-new-york/">"A Fake Artist goes to New
York"</a> by Oink Games Inc.<br />
There is also an official digital version on Steam <a
href="https://store.steampowered.com/app/1933490/Lets_Play_Oink_Games/">Let's Play! Oink Games</a>
</div>
<div class="additional_info" id="source-info">
Sourcecode available on <a href="https://github.com/">GitHub</a> :)
</div>
</body> </body>
</html> </html>

View file

@ -4,7 +4,7 @@ const http = require('http').Server(app);
const io = require('socket.io')(http); const io = require('socket.io')(http);
const cors = require('cors'); const cors = require('cors');
const room_chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; const room_chars = 'ABCDEFGHJKLMOPRSTUWXYZ';
Array.prototype.remove = function() { Array.prototype.remove = function() {
var what, a = arguments, L = a.length, ax; var what, a = arguments, L = a.length, ax;
@ -34,15 +34,23 @@ const available_colors = [
'#500027', '#006241', '#ffc4e4' '#500027', '#006241', '#ffc4e4'
]; ];
var active_games = {};
class Player { class Player {
id = null; id = null;
player_name = 'Player'; player_name = 'Player';
player_color = ''; player_color = '';
score = 0;
ready = false;
constructor(id_, name_, player_color_) { constructor(id_, name_, player_color_) {
this.id = id_; this.id = id_;
this.player_name = name_; this.player_name = name_;
this.player_color = player_color_; this.player_color = player_color_;
this.score = 0;
this.ready = false;
} }
} }
@ -55,35 +63,398 @@ const GAME_STATE = {
} }
class Game { class Game {
// Const
room_id = ''; room_id = '';
// Change on trigger
topic = ''; topic = '';
hint = ''; hint = '';
current_game_state = GAME_STATE.PRE_GAME;
;
players = []; // Player Class players = []; // Player Class
actual_players = []; disconnected_during_session = [];
current_game_state = 0;
current_player = null;
leader = null; leader = null;
fake_artist = null; fake_artist = null;
current_round = 0; // - Per turn
current_turn = 0;
current_player = null;
// - Per Game
player_turns = []; player_turns = [];
disconnected_during_session = []; possible_leaders = [];
actual_players = [];
spectators = [];
turn_drawdata = []; // 2d array, turn -> then array of draw_data dicts
can_vote = [];
player_votes = {};
constructor(room_id_) { constructor(room_id_) {
this.room_id = room_id_ this.room_id = room_id_
} }
add_player(player) { start_game() {
this.current_game_state = GAME_STATE.TOPIC_SELECTION;
io.to(this.room_id).emit('game_started');
this.players.forEach((p) => {
p.ready = false;
});
this.select_leader();
this.prepare_player_turns();
this.select_fake_artist();
this.notify_leader(); // to choose topic
this.spectators = [];
}
select_leader() {
const leader =
this.players[Math.floor(Math.random() * this.players.length)];
this.leader = leader;
var remaining_players = this.players.filter(e => e !== leader);
this.actual_players = remaining_players.sort((a, b) => 0.5 - Math.random());
}
prepare_player_turns() {
this.player_turns = this.actual_players.concat(this.actual_players);
this.turn_drawdata =
[]; // new Array(this.player_turns.length).fill(new Array());
for (var i = 0; i < this.player_turns.length; i++) {
this.turn_drawdata.push([]);
}
}
select_fake_artist() {
const fake_artist = this.actual_players[Math.floor(
Math.random() * this.actual_players.length)];
this.fake_artist = fake_artist;
}
notify_leader() {
this.topic = '';
this.hint = '';
io.to(this.room_id).emit('leader_selected', this.leader);
}
start_voting() {
this.current_game_state = GAME_STATE.VOTING;
this.player_votes = {};
this.can_vote =
this.player_turns.slice(0, [Math.ceil(this.player_turns.length / 2)]);
this.can_vote.forEach((p) => {
this.player_votes[p.id] = 0;
});
console.log('GAME FINISHED!');
io.to(this.room_id).emit('game_finished', this.leader, this.can_vote);
}
end_voting() {}
update_player_list() {
io.to(this.room_id).emit('player_data', this.players);
}
update_votes() {
var vote_data = {};
vote_data.players_voted = [];
const already_voted_players = this.actual_players.filter(
a => !this.can_vote.map(b => b.id).includes(a.id));
/*this.actual_players.filter( el => {
return this.can_vote.some( f => {
return f.id == el.id;
});
});*/
// console.log(already_voted_players);
already_voted_players.forEach((p) => {
vote_data.players_voted.push(p.id);
});
if (this.can_vote.length == 0) {
vote_data.done = true;
// everybody voted, send full data
vote_data.player_votes = this.player_votes;
vote_data.fake_artist = this.fake_artist;
this.current_game_state = GAME_STATE.RESULTS;
io.to(this.room_id).emit('show_vote_results', vote_data);
this.update_player_list();
}
else {
io.to(this.room_id).emit('update_votes', vote_data);
}
}
advance_turn() {
console.log('turn: ', this.current_turn);
if (this.current_game_state == GAME_STATE.VOTING) {
return;
}
this.current_player = this.player_turns[this.current_turn];
if (this.current_turn >= this.player_turns.length) {
this.start_voting();
return;
}
if (this.disconnected_during_session.includes(this.current_player.id)) {
this.current_turn++;
advance_turn();
return;
}
io.to(this.room_id)
.emit('advance_turn', this.current_turn, this.current_player);
this.current_turn++; // because of this we have to -1 current_turn in some
// instances
}
get_next_free_player_color() {
var player_color = available_colors[0];
for (var i = 0; i < available_colors.length; i++) {
var free = true;
for (var k = 0; k < this.players.length; k++) {
if (available_colors[i] == this.players[k].player_color) {
free = false;
break;
}
}
if (free) {
player_color = available_colors[i];
break;
}
}
return player_color;
}
all_players_ready() {
// Check if all players are ready
// and if we have at least 3 players...
if (this.players.length < 3) {
return false;
}
const not_ready = this.players.filter((p) => p.ready == false);
if (not_ready.length > 0) {
return false;
}
return true;
}
is_spectator(player) {
return this.spectators.some((e) => {
return e.id == player.id;
});
}
add_player(socket_, name_) {
const player =
new Player(socket_.id, name_, this.get_next_free_player_color());
this.players.push(player); this.players.push(player);
socket_.join(this.room_id);
socket_.game = this;
this.update_player_list();
if (this.current_game_state != GAME_STATE.PRE_GAME) {
console.log('adding spectator', player.player_name);
this.spectators.push(player);
}
if (this.current_game_state == GAME_STATE.TOPIC_SELECTION) {
socket_.emit('game_started');
socket_.emit('leader_selected', this.leader);
}
// Send current game state data to socket
if (this.current_game_state == GAME_STATE.DRAWING ||
this.current_game_state == GAME_STATE.RESULTS ||
this.current_game_state == GAME_STATE.VOTING) {
socket_.emit('game_started');
socket_.emit('actually_start_game', this.actual_players);
socket_.emit(
'topic_selected', '? (You are a spectator. No cheating!)', this.hint);
for (var turn = 0; turn < this.current_turn; turn++) {
console.log('StateTurn: ', turn, this.turn_drawdata[turn].length);
socket_.emit('advance_turn', turn, this.player_turns[turn]);
for (var idx = 0; idx < this.turn_drawdata[turn].length; idx++) {
console.log('DrawData: ', turn, idx);
socket_.emit('draw_data', turn, this.turn_drawdata[turn][idx]);
}
}
// console.log(JSON.stringify(this.turn_drawdata));
if (this.current_game_state == GAME_STATE.RESULTS ||
this.current_game_state == GAME_STATE.VOTING) {
socket_.emit(
'game_finished', this.leader,
this.player_turns.slice(
0, [Math.ceil(this.player_turns.length / 2)]));
}
}
socket_.on('ready', () => {
console.log(socket_.id);
console.log(this.players);
const p = this.get_player(socket_.id);
if (p) {
p.ready = true;
this.update_player_list();
this.try_start_game();
}
});
socket_.on('draw_data', (arr) => {
if (socket_.id != this.current_player.id) {
return;
}
// console.log(this.current_turn - 1, arr);
this.turn_drawdata[this.current_turn - 1].push(arr);
socket_.broadcast.to(this.room_id)
.emit('draw_data', this.current_turn - 1, arr);
});
socket_.on('topic_selected', (topic, hint) => {
if (this.current_game_state != GAME_STATE.TOPIC_SELECTION) {
return;
}
console.log(topic, hint);
if (socket_.id != this.leader.id) {
// Ignore other people trying to set a topic
return;
}
if (topic.length < 2 || hint.length < 2) {
socket_.emit(
'topic_error',
'The provided topic/hint are too short (min. 2 characters)')
return;
}
this.topic = topic;
this.hint = hint;
console.log(this.spectators);
this.players.forEach((player) => {
console.log(player.id);
if (this.is_spectator(player)) {
io.to(player.id).emit(
'topic_selected', '? (You are a spectator. No cheating!)', hint);
return;
}
if (player.id != this.fake_artist.id) {
console.log('OK');
io.to(player.id).emit('topic_selected', topic, hint);
} else {
console.log('FAKE');
io.to(player.id).emit(
'topic_selected', '? (You are the Fake Artist!)', hint);
}
});
this.current_game_state = GAME_STATE.DRAWING;
io.to(this.room_id).emit('actually_start_game', this.actual_players);
this.current_turn = 0;
this.advance_turn();
});
socket_.on('turn_finished', () => {
if (this.current_game_state != GAME_STATE.DRAWING) {
return;
}
for (var turn = 0; turn < this.current_turn; turn++) {
console.log('DrawDataLength: ', turn, this.turn_drawdata[turn].length);
}
this.advance_turn();
});
socket_.on('vote_for_player', (player_id) => {
if (this.current_game_state != GAME_STATE.VOTING) {
return;
}
if (this.can_vote.filter(e => e.id === socket_.id).length > 0) {
// do vote
this.can_vote = this.can_vote.filter(e => e.id !== socket_.id);
console.log(this.can_vote);
console.log('do vote');
this.player_votes[player_id]++;
this.update_votes();
}
});
socket_.on('disconnect', () => {
console.log('Player disconnected!');
this.remove_player(socket_.id);
// if this was the last player of our game
if (this.players.length == 0) {
delete active_games[this.room_id];
return;
}
this.try_start_game();
});
}
try_start_game() {
if (this.all_players_ready()) {
this.start_game();
}
} }
remove_player(player_id) { remove_player(player_id) {
this.players.splice( this.players.splice(
this.players.findIndex(item => item.id === player_id), 1) this.players.findIndex(item => item.id === player_id), 1);
this.update_player_list();
}
get_player(player_id) {
const idx = this.players.findIndex(item => item.id === player_id);
if (idx != -1) {
return this.players[idx];
}
return null;
} }
} }
var active_games = {};
/* /*
GameLoop: GameLoop:
- Lobby - Lobby
@ -94,19 +465,11 @@ game)
- They take turns, only getting to see the image once its their turn (may be - 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 changed, depending) on how boring it gets to wait (maybe only let game leade
peek) peek)
- Once everybody painted two strokes (two full rounds of the game) - Once everybody painted two strokes (two full turns of the game)
Reveal the image to everybody Reveal the image to everybody
*/ */
initGame = app.use(cors());
() => {
console.log('Initializing Game!');
let count = 0;
// Game.map = {};
// console.log("Map Created: ", Game.map);
}
app.use(cors());
app.use(express.static('static')); app.use(express.static('static'));
app.get('/', (req, res) => { app.get('/', (req, res) => {
@ -116,19 +479,14 @@ app.get('/', (req, res) => {
io.on('connection', (socket) => { io.on('connection', (socket) => {
console.log('User: ', socket.id, ' connected.'); console.log('User: ', socket.id, ' connected.');
socket.on('draw_data', (arr) => { // Create Room
if (socket.room) { // Join Room
var room_id = socket.room; // Ready Up (Pre-Game and for Replay) -> Start game
var game = active_games[room_id]; // Vote cast
// Topic selected (Topic + Hint)
socket.broadcast.to(game.room_id) // turn Finished (Finished Drawing, either by lifting the pen or using up all
.emit('draw_data', game.current_round - 1, arr); // ink) Connect / Disconnect (disconnect is especially important to remove
} // players from games/lobbies)
console.log(arr[0], arr[1]);
});
// socket.on('')
socket.on('create_room', (username) => { socket.on('create_room', (username) => {
if (username == '') { if (username == '') {
@ -136,247 +494,60 @@ io.on('connection', (socket) => {
return; return;
} }
console.log('New room');
var room_id = randomId(); var room_id = randomId();
console.log(room_id); while (room_id in active_games) {
socket.join(room_id); var room_id = randomId();
socket.emit('room', room_id); }
console.log(io.sockets.adapter.rooms);
var game = new Game(room_id); 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; 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.emit(
'join_room',
room_id); // maybe do this in the game instance instead? same as join
game.add_player(socket, username);
game.update_player_list();
}); });
socket.on('start_game', () => {
if (socket.room) {
var room_id = socket.room;
var game = active_games[room_id];
if(game.game_state == GAME_STATE.TOPIC_SELECTION) {
// TODO: Check what happens after replay
return;
}
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.sort((a, b) => 0.5 - Math.random());
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, hint) => {
console.log(topic, hint);
if (socket.room) {
var room_id = socket.room;
var game = active_games[room_id];
if(game.game_state != GAME_STATE.TOPIC_SELECTION) {
return;
}
if(socket.id != game.leader.id) {
// Ignore other people trying to set a topic
return;
}
if(topic.length < 2 || hint.length < 2) {
return;
}
game.topic = topic;
game.hint = hint;
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, hint);
} else {
console.log('FAKE');
io.to(player.id).emit('topic_selected', '', hint);
}
});
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.player_turns.slice(0, [Math.ceil(game.player_turns.length / 2)]));
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) => { socket.on('join_room', (room_id, username) => {
var upper_room_id = room_id.toUpperCase();
if (username == '') { if (username == '') {
socket.emit('room_error', 'Invalid username!'); socket.emit('room_error', 'Invalid username!');
return; return;
} }
if (!active_games[room_id]) { if (!active_games[upper_room_id]) {
socket.emit('room_error', 'This room does not exist!'); socket.emit('room_error', 'This room does not exist!');
return; return;
} }
var user_count = active_games[room_id].players.length; var user_count = active_games[upper_room_id].players.length;
if (user_count > 10) { if (user_count >= 10) {
socket.emit('room_error', 'This room is full!'); socket.emit('room_error', 'This room is full!');
return; return;
} }
var game_state = active_games[room_id].current_game_state; var game = active_games[upper_room_id];
game.add_player(socket, username);
if (game_state != GAME_STATE.PRE_GAME) { socket.emit(
socket.emit('room_error', 'This game is already in progress!'); 'join_room',
return; upper_room_id); // maybe do this in the game instance instead?
} game.update_player_list(); // this could be automated then...
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]));
}); });
// TODO: Refactor/Check what's happening
socket.on('disconnect', () => { socket.on('disconnect', () => {
console.log('User: ', socket.id, ' disconnected.'); console.log('User: ', socket.id, ' disconnected.');
if (socket.room) { if (socket.game) {
var room_id = socket.room; var room_id = socket.game.room_id;
var game = active_games[room_id]; const clients = io.sockets.adapter.rooms.get(room_id);
game.remove_player(room_id); if (clients) {
io.to(room_id).emit('player_data', game.players); for (const clientId of clients) {
console.log(clientId);
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);
} }
} }
} }
@ -387,5 +558,3 @@ io.on('connection', (socket) => {
http.listen(7777, '0.0.0.0', () => { http.listen(7777, '0.0.0.0', () => {
console.log('Listening on 7777'); console.log('Listening on 7777');
}) })
initGame();