commit
3906e45f26
27 changed files with 1997 additions and 0 deletions
@ -0,0 +1 @@ |
||||
.idea |
@ -0,0 +1,81 @@ |
||||
1.0.0 > - Started versioning |
||||
|
||||
- Released first version of the game |
||||
|
||||
- Still in progress, but already playable with a maximum of 4 players |
||||
|
||||
- You cant join running games, looking forward to add a spectator mode |
||||
|
||||
|
||||
|
||||
1.0.1 > - Enabled user feedback and made changelog.txt public |
||||
|
||||
- Menues fade more smoothly |
||||
|
||||
|
||||
|
||||
1.0.2 > - Added tooltip for feedback button when disabled |
||||
|
||||
- Less space for Leaderboard, more space for Gameboard |
||||
|
||||
|
||||
|
||||
1.1.0 > - Added spectator mode: |
||||
|
||||
- Rooms which games have already been started can be joined as spectator |
||||
|
||||
- Room menu allows to choose between being spectator or player |
||||
|
||||
- Players who lose become spectators |
||||
|
||||
|
||||
- Gameplay: |
||||
|
||||
- Players without fields now lose immediatly instead of having the ability to take new fields at their turn |
||||
|
||||
- Players can now also win by being last man standing, without being owner of the required amount of fields |
||||
|
||||
|
||||
- QoL and Fixes: |
||||
|
||||
- Reloading the page is not needed anymore, every menu state can be left |
||||
|
||||
- Added some tooltips |
||||
|
||||
- If a player wins, the winning chainreact doesn't break but plays till the end for visual satisfaction :) |
||||
|
||||
- Fixed a bug where players couldn't join their own created rooms |
||||
|
||||
|
||||
|
||||
1.1.1 > - Fixed a game-breaking bug from the last update where players had multiple turns at once |
||||
|
||||
|
||||
|
||||
1.1.2 > - Fixed a simple bug which resulted in multiple useless log messages |
||||
|
||||
|
||||
|
||||
1.2.0 > - Added editable settings for a game, currently only including field size |
||||
|
||||
|
||||
|
||||
1.2.1 > - Added setting for maximum turn time. Countdown is shown at the top left corner while it's a players turn |
||||
|
||||
- Made explosions a bit fancier |
||||
|
||||
- Player or room names have limitations now... |
||||
|
||||
|
||||
|
||||
1.2.2 > - Added setting to choose if there should be empty fields |
||||
|
||||
- Better explosion animation |
||||
|
||||
- Countdown appearance is clearer |
||||
|
||||
|
||||
|
||||
1.2.3 > - Name limitations are better now ;) |
||||
|
||||
- Changed names of settings |
After Width: | Height: | Size: 318 B |
After Width: | Height: | Size: 679 B |
After Width: | Height: | Size: 585 B |
After Width: | Height: | Size: 782 B |
@ -0,0 +1,33 @@ |
||||
'use strict';
|
||||
|
||||
function keyPressed(){
|
||||
|
||||
} |
||||
|
||||
function keyReleased(){ |
||||
|
||||
} |
||||
|
||||
function mouseMoved(){ |
||||
|
||||
} |
||||
|
||||
function mouseDragged(){ |
||||
|
||||
} |
||||
|
||||
function mousePressed(){ |
||||
if (game) |
||||
if (!game.winId) |
||||
game.onMouseDown(); |
||||
} |
||||
|
||||
function mouseReleased(){ |
||||
|
||||
} |
||||
|
||||
window.onresize = () => { |
||||
let w = $('#canvas-holder').width(); |
||||
let h = $('#canvas-holder').height(); |
||||
resizeCanvas(w, h); |
||||
} |
@ -0,0 +1,579 @@ |
||||
function readGameSettings(){ |
||||
$('.setting').attr('disabled', false); |
||||
|
||||
let fieldSize = parseInt($('#fieldsize_select :selected').val()); |
||||
let turnTime = parseInt($('#turntime_select :selected').val()); |
||||
let mixed = $('#mixed_check').is(':checked'); |
||||
let silent = $('#silent_check').is(':checked'); |
||||
|
||||
gameSettings.fieldSize = fieldSize; |
||||
gameSettings.turnTime = turnTime; |
||||
gameSettings.mixed = mixed; |
||||
gameSettings.silent = silent; |
||||
|
||||
if (mixed){ |
||||
let maxIndex = fieldSize * fieldSize - 1; |
||||
let indices = []; |
||||
for (let i = 0; i < maxIndex * 0.1; i++){ |
||||
let index; |
||||
do{ |
||||
index = floor(random(0, maxIndex)); |
||||
} while (indices.find(i => i === index) != null); |
||||
indices.push(index); |
||||
} |
||||
gameSettings.emptyFieldIndices = indices; |
||||
} |
||||
|
||||
socket.emit('game-settings', gameSettings); |
||||
} |
||||
|
||||
let gameSettings = {}; |
||||
|
||||
class Game{ |
||||
|
||||
constructor(players){ |
||||
this.turn = ''; |
||||
this.playerHues = {'neutral': 0}; |
||||
this.fields = []; |
||||
this.fieldCount = gameSettings.fieldSize; |
||||
this.spreads = []; |
||||
this.spreadsToRemove = []; |
||||
this.bufferedSlots = []; |
||||
|
||||
let i = 0; |
||||
for (let x = 0; x < this.fieldCount; x++){ |
||||
for (let y = 0; y < this.fieldCount; y++){ |
||||
let isEmpty = false; |
||||
if (gameSettings.mixed) |
||||
isEmpty = gameSettings.emptyFieldIndices.find(ei => ei === i) != null; |
||||
this.fields.push(new Field(x, y, this.fieldCount, isEmpty)); |
||||
i++; |
||||
} |
||||
} |
||||
this.fields.forEach(f => f.setup(this.fields)); |
||||
|
||||
this.leaderboard = new Leaderboard(); |
||||
this.countdown = new Countdown(); |
||||
|
||||
this.fieldMarginFactor = 0.1; |
||||
|
||||
socket.on('player-colors', (lobby, colors) => this.setPlayerHues(colors)); |
||||
socket.on('current-turn', (lobby, turnId) => this.setTurnPlayer(lobby.clients.filter(c => c.isPlayer), turnId)); |
||||
socket.on('set-slot', (lobby, fieldsIndex, slotsIndex, id) => this.setSlot(fieldsIndex, slotsIndex, id)); |
||||
|
||||
|
||||
this.players = players; |
||||
} |
||||
|
||||
get winCount(){ |
||||
return round(this.fields.filter(f => !f.isEmpty).length * 0.7); |
||||
} |
||||
|
||||
//Width and height of game field
|
||||
get size(){ |
||||
return Math.min(width, height - this.leaderboard.height); |
||||
} |
||||
|
||||
//Upper left corner of game field
|
||||
get pos(){ |
||||
return {x: (width - this.size) / 2, y: (height + this.leaderboard.height - this.size) / 2}; |
||||
} |
||||
|
||||
get hasTurn(){ |
||||
return this.turn === socket.id |
||||
} |
||||
|
||||
get isSpectator(){ |
||||
return this.players.find(p => p.id === socket.id) == null |
||||
} |
||||
|
||||
get data(){ |
||||
let data = { |
||||
pH: this.playerHues, |
||||
f: [] |
||||
} |
||||
this.fields.forEach((f, fi) => { |
||||
if (!f.isNeutral){ |
||||
let slots = []; |
||||
f.slots.forEach((s, si) => { |
||||
if (s.isFilled) |
||||
slots.push(si); |
||||
}); |
||||
data.f.push({fi: fi, id: f.ownerId, sl: slots, ie: f.isEmpty}); |
||||
} |
||||
}); |
||||
return data; |
||||
} |
||||
|
||||
get players(){ |
||||
return this._players; |
||||
} |
||||
|
||||
set players(players){ |
||||
this.leaderboard.setBarsFromPlayers(players); |
||||
this._players = players; |
||||
this.fields.forEach(f => { |
||||
if (players.find(p => p.id === f.ownerId) == null && !f.isNeutral){ |
||||
f.slots.forEach(s => s.isFilled = false); |
||||
f.ownerId = 'neutral'; |
||||
} |
||||
}); |
||||
} |
||||
|
||||
setPlayerHues(colors){ |
||||
for (let key in colors){ |
||||
this.playerHues[key] = colors[key]; |
||||
let graphics = createGraphics(100, 100); |
||||
graphics.colorMode(HSB); |
||||
graphics.tint(colors[key], 100, 100); |
||||
graphics.image(gemContentImage, 0, 0, 100, 100); |
||||
gemContentGraphics[colors[key]] = graphics; |
||||
} |
||||
} |
||||
|
||||
display(){ |
||||
this.fields.forEach(f => f.display()); |
||||
this.spreads.forEach(s => s.display()); |
||||
|
||||
this.leaderboard.display(); |
||||
this.countdown.display(); |
||||
} |
||||
|
||||
update(){ |
||||
this.spreads.forEach(s => s.update()); |
||||
this.spreads = this.spreads.filter((s, i) => this.spreadsToRemove.find(r => r === i) == null); |
||||
if (!this.spreads.length && this.spreadsToRemove.length){ |
||||
this.sendReady(); |
||||
} |
||||
|
||||
this.spreadsToRemove = []; |
||||
|
||||
|
||||
if (this.countdown.isFinished) |
||||
this.skipTurn(); |
||||
|
||||
if (!this.winId) |
||||
this.checkWin(); |
||||
} |
||||
|
||||
skipTurn(){ |
||||
if (!this.countdown.isChecked && this.hasTurn && !this.doneTurn){ |
||||
this.countdown.isChecked = true; |
||||
this.doneTurn = true; |
||||
socket.emit('set-slot', null, null); |
||||
} |
||||
} |
||||
|
||||
checkWin(){ |
||||
this.players.forEach((p, i, a) => { |
||||
let count = this.fields.filter(f => f.ownerId === p.id).length; |
||||
if (count >= this.winCount || (a.length === 1 && !productionMode)){ |
||||
console.log('Player ' + p.name + ' (' + p.id + ') won the game!'); |
||||
this.winId = p.id; |
||||
refreshGamePlayers(this.players); |
||||
this.countdown.stop(); |
||||
//$('#play_again').show();
|
||||
} |
||||
}); |
||||
} |
||||
|
||||
setTurnPlayer(players, turnId){ |
||||
this.turn = turnId; |
||||
this.doneTurn = false; |
||||
if (!this.winId) |
||||
this.countdown.start(); |
||||
refreshGamePlayers(players); |
||||
} |
||||
|
||||
setSlot(fieldIndex, slotIndex, id){ |
||||
if (fieldIndex == null || slotIndex == null){ |
||||
this.sendReady(); |
||||
return; |
||||
} |
||||
|
||||
if (this.spreads.length){ |
||||
this.bufferedSlots.push({fieldIndex: fieldIndex, slotIndex: slotIndex, id: id}); |
||||
return; |
||||
} |
||||
|
||||
if (id === socket.id) |
||||
this.firstTurnMade = true |
||||
this.fields.forEach(f => f.slots.forEach(s => s.isHighlighted = false)); |
||||
|
||||
let slot = this.fields[fieldIndex].slots[slotIndex]; |
||||
slot.field.ownerId = id; |
||||
slot.isFilled = true; |
||||
|
||||
if (this.players[0]) |
||||
if (socket.id === this.players[0].id) |
||||
socket.emit('game-data', this.data) |
||||
|
||||
if (slot.field.allSlotsFilled){ |
||||
this.createSpreads(slot.field); |
||||
} else { |
||||
if (!gameSettings.silent) |
||||
slot.isHighlighted = true; |
||||
this.sendReady(); |
||||
} |
||||
|
||||
this.countdown.stop(); |
||||
} |
||||
|
||||
setAllSlots(data){ |
||||
data.f.forEach(f => { |
||||
this.fields[f.fi].ownerId = f.id; |
||||
this.fields[f.fi].isEmpty = f.ie; |
||||
this.fields[f.fi].slots.forEach((s, i) => { |
||||
if (f.sl.find(si => si === i) != null){ |
||||
s.isFilled = true; |
||||
} |
||||
}) |
||||
}); |
||||
this.fields.forEach(f => { |
||||
if (f.allSlotsFilled) |
||||
this.createSpreads(f); |
||||
}); |
||||
} |
||||
|
||||
sendReady(){ |
||||
if (this.bufferedSlots.length){ |
||||
let slot = this.bufferedSlots[0]; |
||||
this.bufferedSlots.splice(0, 1); |
||||
this.setSlot(slot.fieldIndex, slot.slotIndex, slot.id); |
||||
} else { |
||||
let isDead = this.fields.filter(f => f.isMine).length === 0 && this.firstTurnMade; |
||||
socket.emit('ready-for-turn', isDead); |
||||
} |
||||
} |
||||
|
||||
createSpreads(field){ |
||||
field.slots.forEach(s => { |
||||
let spread = new Spread(s); |
||||
this.spreads.push(spread); |
||||
}); |
||||
} |
||||
|
||||
onMouseDown(){ |
||||
this.fields.filter(f => f.isMouseOver).forEach(f => f.onMouseDown()); |
||||
} |
||||
|
||||
onPlayerLeft(id){ |
||||
|
||||
} |
||||
} |
||||
|
||||
class Field{ |
||||
|
||||
constructor(x, y, count, isEmpty){ |
||||
this.gridPos = {x: x, y: y}; |
||||
this.count = count; |
||||
this.isEmpty = isEmpty; |
||||
this.slots = []; |
||||
this.ownerId = 'neutral'; |
||||
} |
||||
|
||||
get isTop(){ |
||||
return this.gridPos.y === 0; |
||||
} |
||||
|
||||
get isRight(){ |
||||
return this.gridPos.x === this.count - 1; |
||||
} |
||||
|
||||
get isBottom(){ |
||||
return this.gridPos.y === this.count - 1; |
||||
} |
||||
|
||||
get isLeft(){ |
||||
return this.gridPos.x === 0; |
||||
} |
||||
|
||||
get size(){ |
||||
return game.size / (game.fieldCount + (game.fieldCount + 1) * game.fieldMarginFactor); |
||||
} |
||||
|
||||
get margin(){ |
||||
return game.fieldMarginFactor * this.size; |
||||
} |
||||
|
||||
//Upper left corner of field
|
||||
get pos(){ |
||||
let x = game.pos.x + this.margin + this.gridPos.x * (this.size + this.margin); |
||||
let y = game.pos.y + this.margin + this.gridPos.y * (this.size + this.margin); |
||||
return {x: x, y: y}; |
||||
} |
||||
|
||||
get isMouseOver(){ |
||||
return mouseX > this.pos.x && mouseX < this.pos.x + this.size |
||||
&& mouseY > this.pos.y && mouseY < this.pos.y + this.size; |
||||
} |
||||
|
||||
get isNeutral(){ |
||||
return this.ownerId === 'neutral'; |
||||
} |
||||
|
||||
get isEnemy(){ |
||||
return this.ownerId !== socket.id && !this.isNeutral; |
||||
} |
||||
|
||||
get isMine(){ |
||||
return this.ownerId === socket.id; |
||||
} |
||||
|
||||
get allSlotsFilled(){ |
||||
return this.slots.find(s => !s.isFilled) == null; |
||||
} |
||||
|
||||
setup(allFields){ |
||||
if (!this.isEmpty){ |
||||
for (let angle = 0; angle < TWO_PI; angle += PI / 2){ |
||||
let x = round(sin(angle)); |
||||
let y = round(cos(angle)); |
||||
if (this.getPartner(x, y, allFields)) |
||||
this.slots.push(new Slot(x, y)); |
||||
} |
||||
this.slots.forEach(s => s.field = this); |
||||
} |
||||
} |
||||
|
||||
getPartner(x, y, allFields){ |
||||
return allFields.find(f => |
||||
f.gridPos.x === this.gridPos.x + x && |
||||
f.gridPos.y === this.gridPos.y + y && |
||||
!f.isEmpty |
||||
); |
||||
} |
||||
|
||||
display(){ |
||||
|
||||
if (!this.isEmpty){ |
||||
noStroke(); |
||||
fill(0, 0, 5); |
||||
let size = this.size / 3; |
||||
rect(this.pos.x + size, this.pos.y + size, size, size); |
||||
} |
||||
|
||||
this.slots.forEach(s => s.display()); |
||||
|
||||
let s = this.isNeutral ? 0 : 50; |
||||
stroke(100); |
||||
strokeWeight(1); |
||||
fill(game.playerHues[this.ownerId], s, 60, 0.3); |
||||
rect(this.pos.x, this.pos.y, this.size, this.size); |
||||
} |
||||
|
||||
onMouseDown(){ |
||||
this.slots.filter(s => s.isMouseOver).forEach(s => s.onMouseDown()); |
||||
} |
||||
|
||||
} |
||||
|
||||
class Slot{ |
||||
|
||||
constructor(x, y){ |
||||
this.gridPos = {x: x, y: y}; |
||||
this.field = null; |
||||
this.isFilled = null; |
||||
this.isHighlighted = false; |
||||
} |
||||
|
||||
get size(){ |
||||
return this.field.size / 3; |
||||
} |
||||
|
||||
//Upper left corner of slot
|
||||
get pos(){ |
||||
let x = this.field.pos.x + (this.gridPos.x + 1) * this.size; |
||||
let y = this.field.pos.y + (this.gridPos.y + 1) * this.size; |
||||
return {x: x, y: y}; |
||||
} |
||||
|
||||
get isMouseOver(){ |
||||
return mouseX > this.pos.x && mouseX < this.pos.x + this.size |
||||
&& mouseY > this.pos.y && mouseY < this.pos.y + this.size; |
||||
} |
||||
|
||||
get color(){ |
||||
let h = game.playerHues[this.field.ownerId]; |
||||
let s = this.isFilled ? 100 : 0; |
||||
let b = this.isMouseOver && !this.isFilled ? 90 : 70; |
||||
return color(h, s, b) |
||||
} |
||||
|
||||
get partner(){ |
||||
let fieldIndex = game.fields.findIndex(f => |
||||
f.gridPos.x === this.field.gridPos.x + this.gridPos.x && |
||||
f.gridPos.y === this.field.gridPos.y + this.gridPos.y |
||||
); |
||||
let slotIndex = game.fields[fieldIndex].slots.findIndex(s => |
||||
s.gridPos.x === -this.gridPos.x && |
||||
s.gridPos.y === -this.gridPos.y |
||||
); |
||||
return game.fields[fieldIndex].slots[slotIndex]; |
||||
} |
||||
|
||||
display(){ |
||||
|
||||
fill(this.color); |
||||
noStroke(); |
||||
rect(this.pos.x, this.pos.y, this.size, this.size); |
||||
|
||||
if (this.isFilled){ |
||||
stroke(0, 0, 0); |
||||
strokeWeight(3); |
||||
let x = this.pos.x + this.size / 2; |
||||
let y = this.pos.y + this.size / 2; |
||||
line(this.pos.x, y, this.pos.x + this.size, y); |
||||
line(x, this.pos.y, x, this.pos.y + this.size); |
||||
} |
||||
|
||||
if (this.isHighlighted){ |
||||
stroke(0); |
||||
fill(0); |
||||
let hs = this.size / 2; |
||||
ellipse(this.pos.x + hs, this.pos.y + hs, hs, hs); |
||||
} |
||||
} |
||||
|
||||
onMouseDown(){ |
||||
if (!this.field.isEnemy && game.hasTurn && !game.doneTurn && !this.isFilled){ |
||||
let slotsIndex = this.field.slots.indexOf(this); |
||||
let fieldsIndex = game.fields.indexOf(this.field); |
||||
socket.emit('set-slot', fieldsIndex, slotsIndex); |
||||
|
||||
game.countdown.stop(); |
||||
game.doneTurn = true; |
||||
} |
||||
} |
||||
} |
||||
|
||||
class Spread{ |
||||
|
||||
constructor(slot){ |
||||
this.slot = slot; |
||||
this.ownerId = slot.field.ownerId; |
||||
this.color = slot.color; |
||||
this.moveProgress = 0; |
||||
this.waitTime = 0; |
||||
this.moving = false; |
||||
} |
||||
|
||||
get startPos(){ |
||||
let x = this.slot.field.pos.x + this.slot.size; |
||||
let y = this.slot.field.pos.y + this.slot.size; |
||||
return createVector(x, y); |
||||
} |
||||
|
||||
get endPos(){ |
||||
let x = this.slot.partner.field.pos.x + this.slot.size; |
||||
let y = this.slot.partner.field.pos.y + this.slot.size; |
||||
return createVector(x, y); |
||||
} |
||||
|
||||
get pos(){ |
||||
let step = this.smoothStep(0, 1, this.moveProgress); |
||||
return p5.Vector.lerp(this.startPos, this.endPos, step); |
||||
} |
||||
|
||||
get size(){ |
||||
return this.slot.size; |
||||
} |
||||
|
||||
display(){ |
||||
|
||||
noStroke(); |
||||
let x = this.pos.x + this.size / 2; |
||||
let y = this.pos.y + this.size / 2; |
||||
fill(0, 0, 0); |
||||
ellipse(x, y, this.size * 0.9, this.size * 0.9); |
||||
|
||||
let c = frameCount % 10 >= 5 && !this.moving ? color(0, 0, 0) : this.color; |
||||
fill(c); |
||||
ellipse(x, y, this.size * 0.5, this.size * 0.5); |
||||
} |
||||
|
||||
update(){ |
||||
this.waitTime += 1 / frameRate(); |
||||
if (this.waitTime >= 0.2 && !this.moving){ |
||||
this.moving = true; |
||||
this.slot.isFilled = false; |
||||
this.slot.field.ownerId = 'neutral'; |
||||
} |
||||
if (this.moving){ |
||||
this.moveProgress += 1.5 / frameRate(); |
||||
} |
||||
if (this.moveProgress >= 1) |
||||
this.fillEndSlot(); |
||||
} |
||||
|
||||
fillEndSlot(){ |
||||
game.spreadsToRemove.push(game.spreads.indexOf(this)); |
||||
let endSlot = this.slot.partner; |
||||
let buffered = false; |
||||
if (endSlot.isFilled){ |
||||
if (endSlot.field.allSlotsFilled){ |
||||
buffered = true; |
||||
} else
|
||||
endSlot.field.slots.filter(s => !s.isFilled)[0].isFilled = true; |
||||
} else { |
||||
endSlot.isFilled = true; |
||||
} |
||||
endSlot.field.ownerId = this.ownerId; |
||||
|
||||
if (endSlot.field.allSlotsFilled && !buffered) |
||||
game.createSpreads(endSlot.field); |
||||
} |
||||
|
||||
|
||||
|
||||
smoothStep(start, end, t){ |
||||
function clamp(x, lowLimit, upLimit){ |
||||
if (x < lowLimit) |
||||
x = lowLimit; |
||||
if (x > upLimit) |
||||
x = upLimit; |
||||
return x; |
||||
} |
||||
t = clamp((t - start) / (end - start), 0, 1); |
||||
return t * t * t * (t * (t * 6 - 15) + 10); |
||||
} |
||||
|
||||
} |
||||
|
||||
class Countdown{ |
||||
|
||||
constructor(){ |
||||
this.limit = new Date().getTime(); |
||||
} |
||||
|
||||
get time(){ |
||||
return (this.limit - new Date().getTime()) / 1000; |
||||
} |
||||
|
||||
get isFinished(){ |
||||
return this.time <= 0; |
||||
} |
||||
|
||||
display(){ |
||||
if (!this.isFinished){ |
||||
let panelW = (width - game.size) / 2; |
||||
stroke(200); |
||||
fill(150); |
||||
textSize(panelW / 4); |
||||
let x = panelW / 2; |
||||
let y = game.pos.y + game.size / 2; |
||||
let rounded = floor(this.time); |
||||
text(rounded, x, y); |
||||
} |
||||
} |
||||
|
||||
start(){ |
||||
this.limit = new Date().getTime() + gameSettings.turnTime * 1000; |
||||
this.isChecked = false; |
||||
} |
||||
|
||||
stop(){ |
||||
this.limit = 0; |
||||
} |
||||
|
||||
} |
@ -0,0 +1,104 @@ |
||||
'use strict'; |
||||
|
||||
let projectName = "chainreact"; |
||||
|
||||
let debug = false, |
||||
productionMode = false, |
||||
font, |
||||
localSettings, |
||||
loader; |
||||
|
||||
//Only for online games
|
||||
let socket; |
||||
|
||||
let game; |
||||
let gemContentImage; |
||||
let gemBorderImage; |
||||
let gemContentGraphics = {}; |
||||
|
||||
let antiCacheQuery = '?_=' + new Date().getTime(); |
||||
|
||||
function preload(){ |
||||
localSettings = loadJSON('data/settings/settings.json' + antiCacheQuery, json => { |
||||
console.log('Local settings loaded: ', json); |
||||
}, error => { |
||||
console.log('Local settings failed: ', error); |
||||
}); |
||||
|
||||
font = loadFont('data/styles/Tajawal/Tajawal-Regular.ttf', json => { |
||||
console.log('Local font loaded: ', json); |
||||
}, error => { |
||||
console.log('Local font failed: ', error); |
||||
}); |
||||
|
||||
gemContentImage = loadImage('data/images/gem_content.png', img => { |
||||
console.log('Image loaded: ', img); |
||||
}, error => { |
||||
console.log('Image failed: ' , error); |
||||
}); |
||||
|
||||
gemBorderImage = loadImage('data/images/gem_border.png', img => { |
||||
console.log('Image loaded: ', img); |
||||
}, error => { |
||||
console.log('Image failed: ' , error); |
||||
}); |
||||
|
||||
loadJSON('data/settings/libraries.json' + antiCacheQuery, json => { |
||||
loadScripts(json) |
||||
console.log('BenjoCraeft library scripts loaded: ', json) |
||||
}); |
||||
} |
||||
|
||||
function setup(){ |
||||
canvasSetup(); |
||||
interfaceSetup(); |
||||
} |
||||
|
||||
function draw(){ |
||||
background(0, 0, 10); |
||||
|
||||
if (game){ |
||||
game.display(); |
||||
game.update(); |
||||
} |
||||
|
||||
if (loader){ |
||||
loader.update(); |
||||
loader.display(); |
||||
} |
||||
|
||||
if (debug) debugInformation(); |
||||
} |
||||
|
||||
function canvasSetup(){ |
||||
setFrameRate(60); |
||||
let w = $('#canvas-holder').width(), |
||||
h = $('#canvas-holder').height(); |
||||
let canvas = createCanvas(w, h); |
||||
canvas.parent('canvas-holder'); |
||||
textFont(font); |
||||
textAlign(CENTER, CENTER); |
||||
imageMode(CENTER); |
||||
colorMode(HSB); |
||||
} |
||||
|
||||
function interfaceSetup(){ |
||||
window.onresize(); |
||||
setInterval(() => window.onresize(), 500); |
||||
$('#version').html(localSettings.project.version); |
||||
$('#start_feedback, #give_feedback').attr('disabled', 'disabled'); |
||||
nameTyped($('#main > input')); |
||||
|
||||
$('#main').fadeIn(menuesFadeTime); |
||||
} |
||||
|
||||
function loadScripts(libs){ |
||||
for (let script in libs){ |
||||
if (libs[script]){ |
||||
let url = '/lib/benjocraeft/' + script + '.js' |
||||
$.getScript(url, () => { |
||||
console.log('Successfully loaded script: ', url) |
||||
}); |
||||
} |
||||
} |
||||
} |
@ -0,0 +1,78 @@ |
||||
class Leaderboard{ |
||||
|
||||
constructor(){ |
||||
|
||||
} |
||||
|
||||
get pos(){ |
||||
return createVector(game.pos.x, 0); |
||||
} |
||||
|
||||
get width(){ |
||||
return game.size; |
||||
} |
||||
|
||||
get height(){ |
||||
let h = 0; |
||||
if (this.bars[0]) |
||||
h = this.bars[0].marginY; |
||||
this.bars.forEach(b => h += b.height + b.marginY); |
||||
return h; |
||||
} |
||||
|
||||
setBarsFromPlayers(players){ |
||||
this.bars = []; |
||||
players.forEach(p => this.bars.push(new Bar(p))); |
||||
} |
||||
|
||||
display(){ |
||||
this.bars.forEach(b => b.display()); |
||||
} |
||||
|
||||
} |
||||
|
||||
class Bar{ |
||||
|
||||
constructor(player){ |
||||
this.id = player.id; |
||||
} |
||||
|
||||
get marginX(){ |
||||
return game.size / game.winCount; |
||||
} |
||||
get marginY(){ |
||||
return height * 0.005; |
||||
} |
||||
|
||||
get pos(){ |
||||
let lb = game.leaderboard; |
||||
let x = lb.pos.x + this.marginX; |
||||
let y = lb.pos.y + this.marginY + lb.bars.findIndex(b => b.id === this.id) * (this.height + this.marginY); |
||||
return createVector(x, y); |
||||
} |
||||
|
||||
get height(){ |
||||
return 40; |
||||
} |
||||
|
||||
get width(){ |
||||
return game.leaderboard.width - this.marginX * 2; |
||||
} |
||||
|
||||
display(){ |
||||
let count = game.winCount; |
||||
let imageSize = Math.min(this.height, this.width / count * 0.85); |
||||
let y = this.pos.y + this.height / 2; |
||||
|
||||
let filled = game.fields.filter(f => f.ownerId === this.id).length; |
||||
for (let i = 0; i < count; i++){ |
||||
let x = i * this.width / count + this.width / count / 2 + this.pos.x; |
||||
image(gemBorderImage, x, y, imageSize, imageSize); |
||||
} |
||||
for (let i = 0; i < filled; i++){ |
||||
let x = i * this.width / count + this.width / count / 2 + this.pos.x; |
||||
image(gemContentGraphics[game.playerHues[this.id]], x, y, imageSize, imageSize); |
||||
} |
||||
} |
||||
|
||||
} |
@ -0,0 +1,270 @@ |
||||
'use strict'; |
||||
|
||||
let playerName; |
||||
function nameTyped(dom){ |
||||
playerName = $(dom).val(); |
||||
let disabled = isValid(playerName, 1, 20) ? false : 'disabled'; |
||||
$('#main > button').attr('disabled', disabled); |
||||
} |
||||
function roomTyped(dom){ |
||||
let roomName = $(dom).val(); |
||||
let disabled = isValid(roomName, 1, 30) ? false : 'disabled'; |
||||
$('#create_room').attr('disabled', disabled); |
||||
} |
||||
function isValid(name, min, max){ |
||||
let regExp = /^\w+(?:[' _-]\w+)*$/; |
||||
return name.length >= min && name.length <= max && regExp.test(name); |
||||
} |
||||
|
||||
let menuesFadeTime = 200; |
||||
let connecting = false; |
||||
function socketConnect(project, name = playerName){ |
||||
if (connecting) |
||||
return; |
||||
connecting = true; |
||||
|
||||
let urlQueries = '?game=' + project.name + '&name=' + name; |
||||
$.get('/php/get_nodejs_port.php', port => { |
||||
let url = 'https://' + location.hostname + ':' + port + urlQueries; |
||||
|
||||
socket = io.connect(url); |
||||
socket.on('connect', () => { |
||||
console.log('Connected to ', url); |
||||
$('#start_feedback, #give_feedback').attr('disabled', false); |
||||
socket.on('disconnect', () => { |
||||
connecting = false; |
||||
console.log('Disconnected from ' + url); |
||||
$('#lobby, #room, #game_room').filter(':visible').fadeOut(menuesFadeTime, () => { |
||||
$('#main').fadeIn(menuesFadeTime); |
||||
$('#start_feedback, #give_feedback').attr('disabled', 'disabled'); |
||||
}); |
||||
}); |
||||
socket.emit('room-list'); |
||||
socket.on('room-list', (roomList) => { |
||||
roomList = roomList.filter(r => r.game === project.name); |
||||
console.log('Received room list: ', roomList); |
||||
$('#room_listings').html(''); |
||||
for (let room of roomList){ |
||||
let button = $('<button></button>'); |
||||
button.html(room.name); |
||||
button.addClass('room_listing'); |
||||
button.on('click', () => socket.emit('join-lobby', room.id)); |
||||
$('#room_listings').append(button); |
||||
} |
||||
}); |
||||
$('#main').fadeOut(menuesFadeTime, () => { |
||||
$('#lobby > h1').html('Welcome, ' + name + '!'); |
||||
$('#lobby > input').val(name + "'s room"); |
||||
$('#lobby').fadeIn(menuesFadeTime); |
||||
roomTyped($('#lobby > input')); |
||||
}); |
||||
socket.on('created-lobby', (room) => { |
||||
console.log('You successfully created a room!'); |
||||
$('#create_room').attr('disabled', false); |
||||
$('#lobby').fadeOut(menuesFadeTime, () => { |
||||
roomSetup(room); |
||||
}); |
||||
}); |
||||
socket.on('client-list', (room, clients) => { |
||||
console.log('Received client list: ', clients); |
||||
|
||||
let players = clients.filter(c => c.isPlayer); |
||||
let spectators = clients.filter(c => c.isSpectator); |
||||
let leader = players[0]; |
||||
|
||||
if (game) |
||||
game.players = players; |
||||
|
||||
setInterfacePlayers(players); |
||||
setInterfaceSpectators(spectators); |
||||
|
||||
function setInterfacePlayers(players){ |
||||
$('#player_listings').html(''); |
||||
for (let c of players){ |
||||
let div = $('<div></div>'); |
||||
let span = $('<span></span>'); |
||||
let ready = $('<button></button>'); |
||||
div.addClass('player_listing'); |
||||
ready.addClass('player_ready'); |
||||
ready.on('click', () => { |
||||
if (c.id !== socket.id) |
||||
return; |
||||
let isReady = $(ready).data('isReady'); |
||||
$(ready).data('isReady', !isReady); |
||||
socket.emit('set-ready', !isReady); |
||||
}); |
||||
span.html(c.name); |
||||
ready.css('background-color', c.isReady ? 'green' : 'transparent'); |
||||
ready.data('isReady', c.isReady); |
||||
div.append(span, ready); |
||||
$('#player_listings').append(div); |
||||
} |
||||
|
||||
let allReady = players.find(c => !c.isReady) == null; |
||||
let startClickable = allReady && players.length <= 4 && (players.length > 1 || productionMode); |
||||
if (startClickable && players[0]) |
||||
startClickable = players[0].id === socket.id; |
||||
$('#start_button').attr('disabled', startClickable ? false : 'disabled'); |
||||
|
||||
if (players.find(p => p.id === socket.id)){ |
||||
$('#player_list .join_type').hide(); |
||||
} else { |
||||
$('#player_list .join_type').show(); |
||||
} |
||||
|
||||
refreshGamePlayers(players); |
||||
} |
||||
|
||||
function setInterfaceSpectators(spectators){ |
||||
$('.spectator_listings').html(''); |
||||
spectators.forEach(s => { |
||||
let div = $('<div></div>'); |
||||
div.addClass('spectator_listing'); |
||||
div.html(s.name); |
||||
|
||||
$('.spectator_listings').append(div); |
||||
}); |
||||
if (spectators.find(p => p.id === socket.id)){ |
||||
$('.spectator_list .join_type').hide(); |
||||
} else { |
||||
$('.spectator_list .join_type').show(); |
||||
} |
||||
} |
||||
|
||||
if (leader){ |
||||
if (leader.id === socket.id) |
||||
readGameSettings(); |
||||
else |
||||
disableSettings(); |
||||
} else |
||||
disableSettings(); |
||||
|
||||
function disableSettings(){ |
||||
$('.setting').attr('disabled', 'disabled'); |
||||
} |
||||
}); |
||||
socket.on('member-joined', (room, joinedId, joinedName) => { |
||||
if (joinedId === socket.id){ |
||||
console.log('You joined a room!'); |
||||
$('#lobby').fadeOut(menuesFadeTime, () => { |
||||
if (room.hasStarted){ |
||||
$('#game_room').fadeIn(menuesFadeTime); |
||||
} else { |
||||
roomSetup(room); |
||||
} |
||||
}); |
||||
} |
||||
else |
||||
console.log('A player joined your room: ' + joinedName + ' (' + joinedId + ')'); |
||||
}); |
||||
socket.on('member-left', (room, leftId, leftName) => { |
||||
console.log('A player left your room: ' + leftName + ' (' + leftId + ')'); |
||||
if (game) |
||||
game.onPlayerLeft(leftId); |
||||
}); |
||||
socket.on('start-game', (room, seed) => startGame(room, seed)); |
||||
socket.on('start-spectate', (room, data, hues, turnId) => spectateGame(room, data, hues, turnId)); |
||||
socket.on('game-settings', (room, settings) => applySettings(settings)); |
||||
socket.on('left-lobby', () => { |
||||
console.log('You left a room!'); |
||||
$('#leave_room').get(0).disabled = false; |
||||
game = null; |
||||
socket.removeAllListeners('player-colors'); |
||||
socket.removeAllListeners('current-turn'); |
||||
socket.removeAllListeners('set-slot'); |
||||
$('#room, #game_room').filter(':visible').fadeOut(menuesFadeTime, () => { |
||||
$('#lobby').fadeIn(menuesFadeTime); |
||||
}); |
||||
}); |
||||
}); |
||||
}); |
||||
|
||||
} |
||||
|
||||
function applySettings(settings){ |
||||
for (let key in settings) |
||||
gameSettings[key] = settings[key]; |
||||
|
||||
console.log('Applied game settings: ', gameSettings); |
||||
|
||||
$('#fieldsize_select').val(gameSettings.fieldSize); |
||||
$('#turntime_select').val(gameSettings.turnTime); |
||||
$('#mixed_check').prop('checked', gameSettings.mixed); |
||||
$('#silent_check').prop('checked', gameSettings.silent); |
||||
} |
||||
|
||||
function roomSetup(room){ |
||||
$('#room > h1').html(room.name); |
||||
$('#room').fadeIn(menuesFadeTime); |
||||
} |
||||
|
||||
function createRoom(dom){ |
||||
dom.disabled = true; |
||||
let name = $('#lobby > input').val(); |
||||
if (name === '' || name.length > 30) |
||||
dom.disabled = false; |
||||
else |
||||
socket.emit('create-lobby', localSettings, name); |
||||
} |
||||
|
||||
function leaveRoom(dom){ |
||||
dom.disabled = true; |
||||
socket.emit('leave-lobby'); |
||||
} |
||||
|
||||
function serverStartGame(dom){ |
||||
socket.emit('start-game'); |
||||
} |
||||
|
||||
function startGame(room, seed){ |
||||
randomSeed(seed); |
||||
buildGame(room); |
||||
console.log('Game started'); |
||||
socket.emit('ready-for-turn'); |
||||
} |
||||
|
||||
function spectateGame(room, data, hues, turnId){ |
||||
let players = room.clients.filter(c => c.isPlayer); |
||||
buildGame(room); |
||||
if (data) |
||||
game.setAllSlots(data); |
||||
if (hues) |
||||
game.setPlayerHues(hues); |
||||
if (turnId) |
||||
game.setTurnPlayer(players, turnId); |
||||
refreshGamePlayers(players); |
||||
console.log('Started spectating'); |
||||
} |
||||
|
||||
function buildGame(room){ |
||||
$('#room').filter(':visible').fadeOut(menuesFadeTime, () => { |
||||
$('#game_room').fadeIn(menuesFadeTime); |
||||
$('#play_again').hide(); |
||||
}); |
||||
game = new Game(room.clients.filter(c => c.isPlayer)); |
||||
} |
||||
|
||||
function refreshGamePlayers(players){ |
||||
$('#game_players').html(''); |
||||
for (let c of players){ |
||||
let div = $('<div></div>'); |
||||
div.addClass('game_player'); |
||||
div.html(c.name); |
||||
let color = 'hsl(0, 0%, 50%)'; |
||||
if (game){ |
||||
if (game.turn === c.id){ |
||||
color = 'hsl(' + game.playerHues[c.id] + ', 100%, 50%)'; |
||||
} else { |
||||
color = 'hsl(' + game.playerHues[c.id] + ', 10%, 50%)'; |
||||
} |
||||
if (game.winId === c.id){ |
||||
div.effect('pulsate', 5000, () => { |
||||
div.css('background-color', color); |
||||
}); |
||||
} |
||||
}
|
||||
div.css('background-color', color); |
||||
|
||||
$('#game_players').append(div); |
||||
} |
||||
} |
@ -0,0 +1,8 @@ |
||||
{ |
||||
"collision": false, |
||||
"colorPicker": false, |
||||
"cookie": true, |
||||
"loader": true, |
||||
"prototypes": true, |
||||
"technical": true |
||||
} |
@ -0,0 +1,26 @@ |
||||
{ |
||||
"project": { |
||||
"version": "v1.2.3", |
||||
"name": "chainreact", |
||||
"author": "BenjoCraeft", |
||||
"playerCounts": [1, 2, 3, 4], |
||||
"online": { |
||||
"iceServers": [ |
||||
{ |
||||
"urls": "stun:stun.l.google.com:19302" |
||||
}, |
||||
{ |
||||
"urls": "turn:numb.viagenie.ca", |
||||
"credential": "muazkh", |
||||
"username": "webrtc@live.com" |
||||
} |
||||
] |
||||
} |
||||
}, |
||||
"spectators": true, |
||||
"frameWork": { |
||||
"frameRate": 60, |
||||
"width": null, |
||||
"height": null |
||||
} |
||||
} |
@ -0,0 +1,93 @@ |
||||
Copyright 2018 Boutros International. (https://www.boutrosfonts.com) |
||||
|
||||
This Font Software is licensed under the SIL Open Font License, Version 1.1. |
||||
This license is copied below, and is also available with a FAQ at: |
||||
https://scripts.sil.org/OFL |
||||
|
||||
|
||||
----------------------------------------------------------- |
||||
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 |
||||
----------------------------------------------------------- |
||||
|
||||
PREAMBLE |
||||
The goals of the Open Font License (OFL) are to stimulate worldwide |
||||
development of collaborative font projects, to support the font creation |
||||
efforts of academic and linguistic communities, and to provide a free and |
||||
open framework in which fonts may be shared and improved in partnership |
||||
with others. |
||||
|
||||
The OFL allows the licensed fonts to be used, studied, modified and |
||||
redistributed freely as long as they are not sold by themselves. The |
||||
fonts, including any derivative works, can be bundled, embedded, |
||||
redistributed and/or sold with any software provided that any reserved |
||||
names are not used by derivative works. The fonts and derivatives, |
||||
however, cannot be released under any other type of license. The |
||||
requirement for fonts to remain under this license does not apply |
||||
to any document created using the fonts or their derivatives. |
||||
|
||||
DEFINITIONS |
||||
"Font Software" refers to the set of files released by the Copyright |
||||
Holder(s) under this license and clearly marked as such. This may |
||||
include source files, build scripts and documentation. |
||||
|
||||
"Reserved Font Name" refers to any names specified as such after the |
||||
copyright statement(s). |
||||
|
||||
"Original Version" refers to the collection of Font Software components as |
||||
distributed by the Copyright Holder(s). |
||||
|
||||
"Modified Version" refers to any derivative made by adding to, deleting, |
||||
or substituting -- in part or in whole -- any of the components of the |
||||
Original Version, by changing formats or by porting the Font Software to a |
||||
new environment. |
||||
|
||||
"Author" refers to any designer, engineer, programmer, technical |
||||
writer or other person who contributed to the Font Software. |
||||
|
||||
PERMISSION & CONDITIONS |
||||
Permission is hereby granted, free of charge, to any person obtaining |
||||
a copy of the Font Software, to use, study, copy, merge, embed, modify, |
||||
redistribute, and sell modified and unmodified copies of the Font |
||||
Software, subject to the following conditions: |
||||
|
||||
1) Neither the Font Software nor any of its individual components, |
||||
in Original or Modified Versions, may be sold by itself. |
||||
|
||||
2) Original or Modified Versions of the Font Software may be bundled, |
||||
redistributed and/or sold with any software, provided that each copy |
||||
contains the above copyright notice and this license. These can be |
||||
included either as stand-alone text files, human-readable headers or |
||||
in the appropriate machine-readable metadata fields within text or |
||||
binary files as long as those fields can be easily viewed by the user. |
||||
|
||||
3) No Modified Version of the Font Software may use the Reserved Font |
||||
Name(s) unless explicit written permission is granted by the corresponding |
||||
Copyright Holder. This restriction only applies to the primary font name as |
||||
presented to the users. |
||||
|
||||
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font |
||||
Software shall not be used to promote, endorse or advertise any |
||||
Modified Version, except to acknowledge the contribution(s) of the |
||||
Copyright Holder(s) and the Author(s) or with their explicit written |
||||
permission. |
||||
|
||||
5) The Font Software, modified or unmodified, in part or in whole, |
||||
must be distributed entirely under this license, and must not be |
||||
distributed under any other license. The requirement for fonts to |
||||
remain under this license does not apply to any document created |
||||
using the Font Software. |
||||
|
||||
TERMINATION |
||||
This license becomes null and void if any of the above conditions are |
||||
not met. |
||||
|
||||
DISCLAIMER |
||||
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF |
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT |
||||
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE |
||||
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, |
||||
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL |
||||
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING |
||||
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM |
||||
OTHER DEALINGS IN THE FONT SOFTWARE. |
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -0,0 +1,130 @@ |
||||
/* CSS Created by CSS CHECKBOX */ |
||||
/**********************************/ |
||||
/**** www.CSScheckbox.com *********/ |
||||
|
||||
/*general styles for all CSS Checkboxes*/ |
||||
label { |
||||
-webkit-touch-callout: none; |
||||
-webkit-user-select: none; |
||||
-khtml-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox { |
||||
position: absolute; |
||||
overflow: hidden; |
||||
clip: rect(0 0 0 0); |
||||
height:1px; |
||||
width:1px; |
||||
margin:-1px; |
||||
padding:0; |
||||
border:0; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox + label.css-label { |
||||
padding-left:20px; |
||||
height:15px; |
||||
display:inline-block; |
||||
line-height:15px; |
||||
background-repeat:no-repeat; |
||||
background-position: 0 0; |
||||
font-size:15px; |
||||
vertical-align:middle; |
||||
cursor:pointer; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox:checked + label.css-label { |
||||
background-position: 0 -15px; |
||||
} |
||||
|
||||
.css-label{ |
||||
background-image:url(https://csscheckbox.com/checkboxes/dark-check-green.png); |
||||
} |
||||
|
||||
/*specific classes related to Checkbox skins*/ |
||||
|
||||
.lite-green-check{background-image:url(https://csscheckbox.com/checkboxes/lite-green-check.png);} |
||||
.lite-blue-check{background-image:url(https://csscheckbox.com/checkboxes/lite-blue-check.png);} |
||||
.lite-gray-check{background-image:url(https://csscheckbox.com/checkboxes/lite-gray-check.png);} |
||||
.lite-cyan-check{background-image:url(https://csscheckbox.com/checkboxes/lite-cyan-check.png);} |
||||
.lite-orange-check{background-image:url(https://csscheckbox.com/checkboxes/lite-orange-check.png);} |
||||
.lite-red-check{background-image:url(https://csscheckbox.com/checkboxes/lite-red-check.png);} |
||||
|
||||
.lite-x-cyan{background-image:url(https://csscheckbox.com/checkboxes/lite-x-cyan.png);} |
||||
.lite-x-gray{background-image:url(https://csscheckbox.com/checkboxes/lite-x-gray.png);} |
||||
.lite-x-blue{background-image:url(https://csscheckbox.com/checkboxes/lite-x-blue.png);} |
||||
.lite-x-orange{background-image:url(https://csscheckbox.com/checkboxes/lite-x-orange.png);} |
||||
.lite-x-red{background-image:url(https://csscheckbox.com/checkboxes/lite-x-red.png);} |
||||
.lite-x-green{background-image:url(https://csscheckbox.com/checkboxes/lite-x-green.png);} |
||||
|
||||
.mac-style{background-image:url(https://csscheckbox.com/checkboxes/mac-style.png);} |
||||
.mario-style{background-image:url(https://csscheckbox.com/checkboxes/mario-style.png);} |
||||
.alert-style{background-image:url(https://csscheckbox.com/checkboxes/alert-style.png);} |
||||
.lite-plus{background-image:url(https://csscheckbox.com/checkboxes/lite-plus.png);} |
||||
.dark-plus{background-image:url(https://csscheckbox.com/checkboxes/dark-plus.png);} |
||||
.dark-plus-cyan{background-image:url(https://csscheckbox.com/checkboxes/dark-plus-cyan.png);} |
||||
.dark-plus-orange{background-image:url(https://csscheckbox.com/checkboxes/dark-plus-orange.png);} |
||||
.dark-check-cyan{background-image:url(https://csscheckbox.com/checkboxes/dark-check-cyan.png);} |
||||
.dark-check-green{background-image:url(https://csscheckbox.com/checkboxes/dark-check-green.png);} |
||||
.dark-check-orange{background-image:url(https://csscheckbox.com/checkboxes/dark-check-orange.png);} |
||||
|
||||
|
||||
.depressed-lite-small{background-image:url(https://csscheckbox.com/checkboxes/depressed-lite-small.png);} |
||||
.elegant{background-image:url(https://csscheckbox.com/checkboxes/elegant.png);} |
||||
.depressed{background-image:url(https://csscheckbox.com/checkboxes/depressed.png);} |
||||
.chrome-style{background-image:url(https://csscheckbox.com/checkboxes/chrome-style.png);} |
||||
.web-two-style{background-image:url(https://csscheckbox.com/checkboxes/web-two-style.png);} |
||||
.vlad{background-image:url(https://csscheckbox.com/checkboxes/vlad.png);} |
||||
.klaus{background-image:url(https://csscheckbox.com/checkboxes/klaus.png);} |
||||
|
||||
input[type=checkbox].css-checkbox.med + label.css-label.med { |
||||
padding-left:22px; |
||||
height:17px; |
||||
display:inline-block; |
||||
line-height:17px; |
||||
background-repeat:no-repeat; |
||||
background-position: 0 0; |
||||
font-size:15px; |
||||
vertical-align:middle; |
||||
cursor:pointer; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox.med:checked + label.css-label.med { |
||||
|
||||
background-position: 0 -17px; |
||||
} |
||||
input[type=checkbox].css-checkbox.sme + label.css-label.sme { |
||||
padding-left:22px; |
||||
height:16px; |
||||
display:inline-block; |
||||
line-height:16px; |
||||
background-repeat:no-repeat; |
||||
background-position: 0 0; |
||||
font-size:15px; |
||||
vertical-align:middle; |
||||
cursor:pointer; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox.sme:checked + label.css-label.sme{ |
||||
|
||||
background-position: 0 -16px; |
||||
} |
||||
input[type=checkbox].css-checkbox.lrg + label.css-label.lrg { |
||||
padding-left:22px; |
||||
height:20px; |
||||
display:inline-block; |
||||
line-height:20px; |
||||
background-repeat:no-repeat; |
||||
background-position: 0 0; |
||||
font-size:15px; |
||||
vertical-align:middle; |
||||
cursor:pointer; |
||||
} |
||||
|
||||
input[type=checkbox].css-checkbox.lrg:checked + label.css-label.lrg{ |
||||
|
||||
background-position: 0 -20px; |
||||
} |
||||
|
@ -0,0 +1,88 @@ |
||||
#color_picker{ |
||||
width: 300px; |
||||
height: 25%; |
||||
margin: 20px; |
||||
margin-top: 50px; |
||||
border: 5px solid #000; |
||||
background-color: #000; |
||||
-webkit-user-select: none; |
||||
-moz-user-select: none; |
||||
-ms-user-select: none; |
||||
user-select: none; |
||||
position: relative; |
||||
} |
||||
#color_picker_numeric{ |
||||
width: 80%; |
||||
padding: 5%; |
||||
margin: 5%; |
||||
background-color: #888; |
||||
border-radius: 10px; |
||||
overflow: hidden; |
||||
} |
||||
.color_picker_rgb{ |
||||
float: left; |
||||
width: 22%; |
||||
height: 35px; |
||||
font-size: 25px; |
||||
color: #000; |
||||
} |
||||
.color_picker_rgb:nth-child(1){ |
||||
margin-right: 10%; |
||||
margin-left: 3%; |
||||
background-color: #F00; |
||||
|
||||
} |
||||
.color_picker_rgb:nth-child(2){ |
||||
background-color: #0F0; |
||||
} |
||||
.color_picker_rgb:nth-child(3){ |
||||
margin-left: 10%; |
||||
background-color: #00F; |
||||
color: #FFF; |
||||
} |
||||
#color_picker_hex{ |
||||
width: 50%; |
||||
height: 30px; |
||||
font-size: 25px; |
||||
margin: 10% 25% 0 25%; |
||||
} |
||||
#saturation{ |
||||
position: relative; |
||||
width: calc(100% - 33px); |
||||
height: 100%; |
||||
background: linear-gradient(to right, #FFF 0%, #F00 100%); |
||||
float: left; |
||||
margin-right: 6px; |
||||
} |
||||
#value { |
||||
width: 100%; |
||||
height: 100%; |
||||
background: linear-gradient(to top, #000 0%, rgba(255,255,255,0) 100%); |
||||
} |
||||
#sb_picker{ |
||||
border: 2px solid; |
||||
border-color: #FFF; |
||||
position: absolute; |
||||
width: 14px; |
||||
height: 14px; |
||||
border-radius: 10px; |
||||
bottom: 50px; |
||||
left: 50px; |
||||
box-sizing: border-box; |
||||
z-index: 10; |
||||
} |
||||
#hue { |
||||
width: 27px; |
||||
height: 100%; |
||||
position: relative; |
||||
float: left; |
||||
background: linear-gradient(to bottom, #F00 0%, #F0F 17%, #00F 34%, #0FF 50%, #0F0 67%, #FF0 84%, #F00 100%); |
||||
} |
||||
#hue_picker { |
||||
position: absolute; |
||||
background: #000; |
||||
border-bottom: 1px solid #000; |
||||
top: 0; |
||||
width: 27px; |
||||
height: 2px; |
||||
} |
Binary file not shown.
@ -0,0 +1,88 @@ |
||||
input[type=range] { |
||||
-webkit-appearance: none; |
||||
margin: 18px 0; |
||||
width: 100%; |
||||
} |
||||
input[type=range]:focus { |
||||
outline: none; |
||||
} |
||||
input[type=range]::-webkit-slider-runnable-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
background: #3071a9; |
||||
border-radius: 1.3px; |
||||
border: 0.2px solid #010101; |
||||
} |
||||
input[type=range]::-webkit-slider-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
-webkit-appearance: none; |
||||
margin-top: -14px; |
||||
} |
||||
input[type=range]:focus::-webkit-slider-runnable-track { |
||||
background: #367ebd; |
||||
} |
||||
input[type=range]::-moz-range-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
background: #3071a9; |
||||
border-radius: 1.3px; |
||||
border: 0.2px solid #010101; |
||||
} |
||||
input[type=range]::-moz-range-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
} |
||||
input[type=range]::-ms-track { |
||||
width: 100%; |
||||
height: 8.4px; |
||||
cursor: pointer; |
||||
animate: 0.2s; |
||||
background: transparent; |
||||
border-color: transparent; |
||||
border-width: 16px 0; |
||||
color: transparent; |
||||
} |
||||
input[type=range]::-ms-fill-lower { |
||||
background: #2a6495; |
||||
border: 0.2px solid #010101; |
||||
border-radius: 2.6px; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
} |
||||
input[type=range]::-ms-fill-upper { |
||||
background: #3071a9; |
||||
border: 0.2px solid #010101; |
||||
border-radius: 2.6px; |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
} |
||||
input[type=range]::-ms-thumb { |
||||
box-shadow: 1px 1px 1px #000000, 0px 0px 1px #0d0d0d; |
||||
border: 1px solid #000000; |
||||
height: 36px; |
||||
width: 16px; |
||||
border-radius: 3px; |
||||
background: #ffffff; |
||||
cursor: pointer; |
||||
} |
||||
input[type=range]:focus::-ms-fill-lower { |
||||
background: #3071a9; |
||||
} |
||||
input[type=range]:focus::-ms-fill-upper { |
||||
background: #367ebd; |
||||
} |
@ -0,0 +1,142 @@ |
||||
<!-- Web project created by Benjo Craeft (alias) --> |
||||
<!DOCTYPE html> |
||||
<html lang="en"> |
||||
<head> |
||||
<meta charset="utf-8"> |
||||
<script src="/lib/socket.io/socket.io.min.js" type="text/javascript"></script> |
||||
<script src="/lib/socket.io/socket.io-p2p.min.js" type="text/javascript"></script> |
||||
<script src="/lib/p5/p5.min.js" type="text/javascript"></script> |
||||
<script src="/lib/p5/p5.dom.min.js" type="text/javascript"></script> |
||||
<script src="/lib/p5/p5.sound.min.js" type="text/javascript"></script> |
||||
<script src="/lib/jquery/jquery.min.js" type="text/javascript"></script> |
||||
<script src="/lib/jquery/jquery-ui.min.js" type="text/javascript"></script> |
||||
<script src="/lib/vue/vue.js" type="text/javascript"></script> |
||||
<script src="data/scripts/init.js" type="text/javascript"></script> |
||||
<script src="data/scripts/events.js" type="text/javascript"></script> |
||||
<script src="data/scripts/online.js" type="text/javascript"></script> |
||||
<script src="data/scripts/game.js" type="text/javascript"></script> |
||||
<script src="data/scripts/leaderboard.js" type="text/javascript"></script> |
||||
<link href="styles.css" rel="stylesheet"> |
||||
<link href="data/styles/color_picker.css" rel="stylesheet"> |
||||
<link href="data/styles/range_input.css" rel="stylesheet"> |
||||
<link href="data/styles/checkbox.css" rel="stylesheet"> |
||||
<link href="data/images/favicon.ico" rel="icon" type="image/x-icon"> |
||||
<title>Chainreact</title> |
||||
</head> |
||||
<body> |
||||
<div id="content"> |
||||
<div id="interface"> |
||||
<div id="main" style="display: none;"> |
||||
<h1>Enter your Nickname!</h1> |
||||
<input oninput="nameTyped(this)" type="text"> |
||||
<button class="tooltip" onclick="socketConnect(localSettings.project);"> |
||||
Connect & Play! |
||||
<span class="tooltiptext">Enter a name (max. 20 characters)!</span> |
||||
</button> |
||||
</div> |
||||
<div id="lobby" style="display: none;"> |
||||
<h1>Welcome, Benjo</h1> |
||||
<button class="tooltip" id="create_room" onclick="createRoom(this);"> |
||||
Create room |
||||
<span class="tooltiptext">Enter a room name (Max. 30 characters)!</span> |
||||
</button> |
||||
<input oninput="roomTyped(this);" type="text"> |
||||
<fieldset class="fieldset_container" id="room_list"> |
||||
<legend>Available rooms</legend> |
||||
<div id="room_listings"></div> |
||||
</fieldset> |
||||
<button onclick="socket.disconnect()">Disconnect</button> |
||||
</div> |
||||
<div id="room" style="display: none;"> |
||||
<h1></h1> |
||||
<button class="tooltip" id="start_button" onclick="serverStartGame(this);"> |
||||
Start |
||||
<span class="tooltiptext">Leader can start when all players (2-4) are ready!</span> |
||||
</button> |
||||
<fieldset class="fieldset_container" id="settings_list"> |
||||
<legend>Settings</legend> |
||||
<table id="settings" onchange="readGameSettings()"> |
||||
<tr> |
||||
<td>Turn time:</td> |
||||
<td><select class="setting" id="turntime_select"> |
||||
<option value="5">5s</option> |
||||
<option value="10">10s</option> |
||||
<option value="15">15s</option> |
||||
<option value="20">20s</option> |
||||
<option value="25">25s</option> |
||||
<option value="30">30s</option> |
||||
<option value="35">35s</option> |
||||
<option value="50">40s</option> |
||||
<option value="45">45s</option> |
||||
<option value="50">50s</option> |
||||
<option value="55">55s</option> |
||||
<option value="60">60s</option> |
||||
</select></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Field size:</td> |
||||
<td><select class="setting" id="fieldsize_select"> |
||||
<option value="4">4x4</option> |
||||
<option value="5">5x5</option> |
||||
<option value="6">6x6</option> |
||||
<option value="7">7x7</option> |
||||
<option value="8">8x8</option> |
||||
<option value="9">9x9</option> |
||||
</select></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Walls:</td> |
||||
<td><input class="setting" id="mixed_check" type="checkbox"/></td> |
||||
</tr> |
||||
<tr> |
||||
<td>Silent:</td> |
||||
<td><input class="setting" id="silent_check" type="checkbox"></td> |
||||
</tr> |
||||
</table> |
||||
</fieldset> |
||||
<fieldset class="spectator_list fieldset_container"> |
||||
<legend>Spectators</legend> |
||||
<button class="join_type" onclick="socket.emit('join-spectators');">Join</button> |
||||
<div class="spectator_listings"></div> |
||||
</fieldset> |
||||
<fieldset class="fieldset_container" id="player_list"> |
||||
<legend>Players</legend> |
||||
<button class="join_type" onclick="socket.emit('join-players');">Join</button> |
||||
<div id="player_listings"></div> |
||||
</fieldset> |
||||
<button onclick="leaveRoom(this);">Leave</button> |
||||
</div> |
||||
<div id="game_room" style="display: none;"> |
||||
<fieldset class="spectator_list fieldset_container"> |
||||
<legend>Spectators</legend> |
||||
<div class="spectator_listings"></div> |
||||
</fieldset> |
||||
<fieldset class="fieldset_container" id="game_players_holder"> |
||||
<legend>Players</legend> |
||||
<div id="game_players"></div> |
||||
</fieldset> |
||||
<button id="play_again" onclick="sendPlayAgain(this)">Play again!</button> |
||||
<button id="leave_room" onclick="leaveRoom(this);">Leave</button> |
||||
</div> |
||||
<div id="project_info"> |
||||
<span id="version"></span> |
||||
<a href="./changelog.txt" target="_blank">Changelog</a> |
||||
<button class="tooltip" id="start_feedback" onclick="$('#user_feedback').show();givingFeedback = true;"> |
||||
Feedback |
||||
<span class="tooltiptext">Connect to send feedback!</span> |
||||
</button> |
||||
</div> |
||||
<div id="user_feedback" style="display: none;"> |
||||
<span>Write me feedback about bugs/features!</span> |
||||
<textarea cols="50" rows="10"></textarea> |
||||
<button class="tooltip" id="give_feedback" onclick="$.post('/php/post_feedback.php', {content: $('#user_feedback > textarea').val(), projectName: localSettings.project.name});$('#user_feedback').hide();givingFeedback = false;"> |
||||
Submit |
||||
<span class="tooltiptext">Connect to send feedback!</span> |
||||
</button> |
||||
<button onclick="$('#user_feedback').hide();givingFeedback = false;">Cancel</button> |
||||
</div> |
||||
</div> |
||||
<div id="canvas-holder"></div> |
||||
</div> |
||||
</body> |
||||
</html> |
@ -0,0 +1,276 @@ |
||||
a:link, a:hover, a:active, a:visited{color: #000;} |
||||
|
||||
html, body{margin: 0; padding: 0; height: 100%; width: 100%;} |
||||
|
||||
canvas{margin: 0; padding: 0; border: none; display: block;} |
||||
|
||||
button:not([disabled]){cursor: pointer;} |
||||
|
||||
@font-face{ |
||||
font-family: "Rametto"; |
||||
src: url("data/styles/font.ttf"); |
||||
} |
||||
|
||||
@font-face{ |
||||
font-family: "Tajawal"; |
||||
src: url("data/styles/Tajawal/Tajawal-Regular.ttf"); |
||||
} |
||||
|
||||
*{ |
||||
font-family: "Tajawal"; |
||||
color: #000; |
||||
font-size: 17px; |
||||
} |
||||
|
||||
:root{ |
||||
--if-width: 300px; |
||||
--if-border-width: 5px; |
||||
--if-padding: 25px; |
||||
--if-content-width: calc(var(--if-width) - var(--if-padding) * 2); |
||||
--if-background-color: rgb(70, 70, 70); |
||||
--button-color: rgb(40, 143, 228); |
||||
} |
||||
|
||||
.tooltip > .tooltiptext { |
||||
visibility: hidden; |
||||
width: 150px; |
||||
background-color: var(--button-color); |
||||
text-align: center; |
||||
padding: 5px; |
||||
border-radius: 5px; |
||||
|
||||
/* Position the tooltip text */ |
||||
position: absolute; |
||||
z-index: 1; |
||||
bottom: 145%; |
||||
left: 50%; |
||||
margin-left: -80px; |
||||
|
||||
/* Fade in tooltip */ |
||||
opacity: 0; |
||||
transition: opacity 0.1s; |
||||
} |
||||
|
||||
.tooltip:hover:disabled > .tooltiptext { |
||||
visibility: visible; |
||||
opacity: 1; |
||||
} |
||||
.tooltip > .tooltiptext::after { |
||||
content: ""; |
||||
position: absolute; |
||||
top: 100%; |
||||
left: 50%; |
||||
margin-left: -8px; |
||||
border-width: 8px; |
||||
border-style: solid; |
||||
border-color: rgb(34, 137, 189) transparent transparent transparent; |
||||
} |
||||
|
||||
#project_info{ |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: var(--if-width); |
||||
display: flex; |
||||
align-items: baseline; |
||||
} |
||||
#project_info > *{ |
||||
margin: 5px; |
||||
font-size: 20px; |
||||
height: 30px; |
||||
} |
||||
|
||||
#user_feedback{ |
||||
position: absolute; |
||||
top: 0; |
||||
right: 0; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
background-color: rgba(83, 83, 83, 0.699); |
||||
text-align: center; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
#user_feedback > *{ |
||||
width: 50%; |
||||
padding: 5px; |
||||
box-sizing: content-box; |
||||
margin: 5px; |
||||
} |
||||
#user_feedback > span{ |
||||
font-size: 40px; |
||||
margin: 20px; |
||||
} |
||||
|
||||
|
||||
/** |
||||
* Standard styles |
||||
*/ |
||||
|
||||
#content{ |
||||
display: grid; |
||||
grid-template-columns: |
||||
calc(var(--if-width) + var(--if-border-width)) |
||||
calc(100% - (var(--if-width) + var(--if-border-width)) * 1) |
||||
/**calc(var(--if-width) + var(--if-border-width)) */ |
||||
; |
||||
grid-template-rows: 100%; |
||||
width: 100%; |
||||
height: 100%; |
||||
} |
||||
|
||||
#interface{ |
||||
background-color: var(--if-background-color); |
||||
border: var(--if-border-width) solid rgb(200, 200, 200); |
||||
} |
||||
|
||||
#p5_loading{ |
||||
display: none; |
||||
} |
||||
|
||||
|
||||
|
||||
/** |
||||
Interface |
||||
*/ |
||||
#interface{ |
||||
border-width: 0 var(--if-border-width) 0 0; |
||||
} |
||||
#main, #lobby, #room, #game_room{ |
||||
height: 100%; |
||||
display: flex; |
||||
flex-direction: column; |
||||
justify-content: center; |
||||
align-items: center; |
||||
} |
||||
h1{ |
||||
font-size: 30px; |
||||
margin: 10px; |
||||
width: 80%; |
||||
word-wrap: break-word; |
||||
text-align: center; |
||||
} |
||||
input[type=text]{ |
||||
border: 2px solid; |
||||
padding: 0; |
||||
width: 200px; |
||||
height: 30px; |
||||
font-size: 25px; |
||||
margin: 10px; |
||||
text-align: center; |
||||
} |
||||
button:not(.player_ready){ |
||||
width: 200px; |
||||
height: 40px; |
||||
margin: 10px; |
||||
font-size: 25px; |
||||
background-color: var(--button-color); |
||||
border: 2px solid; |
||||
} |
||||
button:hover:not([disabled]){ |
||||
filter: brightness(150%); |
||||
} |
||||
button:disabled{ |
||||
filter: brightness(70%); |
||||
} |
||||
#interface > div:not(#project_info):not(#user_feedback) > *:last-child{ |
||||
margin-bottom: 45px; |
||||
} |
||||
.fieldset_container{ |
||||
width: 200px; |
||||
height: auto; |
||||
min-height: 100px; |
||||
overflow-y: auto; |
||||
background-color: rgb(50, 50, 50); |
||||
border: 5px solid #000; |
||||
margin-bottom: 10px; |
||||
} |
||||
.fieldset_container > div{ |
||||
display: flex; |
||||
flex-direction: column; |
||||
align-items: center; |
||||
} |
||||
.fieldset_container > div > *{ |
||||
width: 180px; |
||||
height: auto; |
||||
margin: 10px 0; |
||||
word-wrap: break-word; |
||||
} |
||||
legend{ |
||||
font-size: 25px; |
||||
} |
||||
|
||||
table{ |
||||
width: inherit; |
||||
} |
||||
td{ |
||||
font-size: 20px; |
||||
} |
||||
td:nth-child(1){ |
||||
width: 60%; |
||||
} |
||||
td:nth-child(2){ |
||||
width: 40%; |
||||
} |
||||
td > input, td > select{ |
||||
border-radius: 2px; |
||||
border: 1px solid #000; |
||||
background-color: rgb(95, 95, 95); |
||||
width: 100%; |
||||
} |
||||
|
||||
#lobby > input{ |
||||
margin-bottom: 50px; |
||||
} |
||||
.player_listing{ |
||||
display: flex; |
||||
flex-direction: row; |
||||
align-items: center; |
||||
justify-content: flex-end; |
||||
background-color: rgb(80, 80, 80); |
||||
border-radius: 5px; |
||||
} |
||||
.player_listing > span{ |
||||
margin-right: 15px; |
||||
font-size: 30px; |
||||
text-align: center; |
||||
word-wrap: break-word; |
||||
width: 126px; |
||||
} |
||||
.player_ready{ |
||||
margin-right: 5px; |
||||
width: 30px; |
||||
height: 30px; |
||||
background-color: none; |
||||
border-radius: 4px; |
||||
border: 2px solid #000; |
||||
} |
||||
|
||||
.spectator_listing{ |
||||
text-align: center; |
||||
font-size: 30px; |
||||
border-radius: 5px; |
||||
background-color: rgb(100, 100, 100); |
||||
} |
||||
|
||||
#game_players_holder{ |
||||
height: auto; |
||||
overflow-y: hidden; |
||||
} |
||||
.game_player{ |
||||
font-size: 35px; |
||||
text-align: center; |
||||
width: 180px; |
||||
word-wrap: wrap; |
||||
border-radius: 5px; |
||||
padding: 10px; |
||||
} |
||||
|
||||
.join_type{ |
||||
width: 100%; |
||||
margin-bottom: 5px; |
||||
} |
Loading…
Reference in new issue