commit 3906e45f26cf1320d8c222779e7d674234424adb Author: Benjamin Kraft Date: Tue Nov 15 10:15:31 2022 +0100 Initial Commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..485dee6 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +.idea diff --git a/changelog.txt b/changelog.txt new file mode 100644 index 0000000..b559103 --- /dev/null +++ b/changelog.txt @@ -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 \ No newline at end of file diff --git a/data/images/favicon.ico b/data/images/favicon.ico new file mode 100644 index 0000000..3172667 Binary files /dev/null and b/data/images/favicon.ico differ diff --git a/data/images/gem_border.png b/data/images/gem_border.png new file mode 100644 index 0000000..4310f4a Binary files /dev/null and b/data/images/gem_border.png differ diff --git a/data/images/gem_content.png b/data/images/gem_content.png new file mode 100644 index 0000000..f0c3819 Binary files /dev/null and b/data/images/gem_content.png differ diff --git a/data/images/gem_full.png b/data/images/gem_full.png new file mode 100644 index 0000000..96e6cf2 Binary files /dev/null and b/data/images/gem_full.png differ diff --git a/data/scripts/events.js b/data/scripts/events.js new file mode 100644 index 0000000..652bc81 --- /dev/null +++ b/data/scripts/events.js @@ -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); +} \ No newline at end of file diff --git a/data/scripts/game.js b/data/scripts/game.js new file mode 100644 index 0000000..4f7039c --- /dev/null +++ b/data/scripts/game.js @@ -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; + } + +} \ No newline at end of file diff --git a/data/scripts/init.js b/data/scripts/init.js new file mode 100644 index 0000000..87bebc0 --- /dev/null +++ b/data/scripts/init.js @@ -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) + }); + } + } +} \ No newline at end of file diff --git a/data/scripts/leaderboard.js b/data/scripts/leaderboard.js new file mode 100644 index 0000000..5551d3a --- /dev/null +++ b/data/scripts/leaderboard.js @@ -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); + } + } + +} \ No newline at end of file diff --git a/data/scripts/online.js b/data/scripts/online.js new file mode 100644 index 0000000..33e1602 --- /dev/null +++ b/data/scripts/online.js @@ -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.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 = $('
'); + let span = $(''); + let ready = $(''); + 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.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.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); + } +} \ No newline at end of file diff --git a/data/settings/libraries.json b/data/settings/libraries.json new file mode 100644 index 0000000..1264577 --- /dev/null +++ b/data/settings/libraries.json @@ -0,0 +1,8 @@ +{ + "collision": false, + "colorPicker": false, + "cookie": true, + "loader": true, + "prototypes": true, + "technical": true +} \ No newline at end of file diff --git a/data/settings/settings.json b/data/settings/settings.json new file mode 100644 index 0000000..da6ea94 --- /dev/null +++ b/data/settings/settings.json @@ -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 + } +} \ No newline at end of file diff --git a/data/styles/Tajawal/OFL.txt b/data/styles/Tajawal/OFL.txt new file mode 100644 index 0000000..b389da1 --- /dev/null +++ b/data/styles/Tajawal/OFL.txt @@ -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. diff --git a/data/styles/Tajawal/Tajawal-Black.ttf b/data/styles/Tajawal/Tajawal-Black.ttf new file mode 100644 index 0000000..19d52ed Binary files /dev/null and b/data/styles/Tajawal/Tajawal-Black.ttf differ diff --git a/data/styles/Tajawal/Tajawal-Bold.ttf b/data/styles/Tajawal/Tajawal-Bold.ttf new file mode 100644 index 0000000..159a3b5 Binary files /dev/null and b/data/styles/Tajawal/Tajawal-Bold.ttf differ diff --git a/data/styles/Tajawal/Tajawal-ExtraBold.ttf b/data/styles/Tajawal/Tajawal-ExtraBold.ttf new file mode 100644 index 0000000..c44b766 Binary files /dev/null and b/data/styles/Tajawal/Tajawal-ExtraBold.ttf differ diff --git a/data/styles/Tajawal/Tajawal-ExtraLight.ttf b/data/styles/Tajawal/Tajawal-ExtraLight.ttf new file mode 100644 index 0000000..5956e03 Binary files /dev/null and b/data/styles/Tajawal/Tajawal-ExtraLight.ttf differ diff --git a/data/styles/Tajawal/Tajawal-Light.ttf b/data/styles/Tajawal/Tajawal-Light.ttf new file mode 100644 index 0000000..95493f5 Binary files /dev/null and b/data/styles/Tajawal/Tajawal-Light.ttf differ diff --git a/data/styles/Tajawal/Tajawal-Medium.ttf b/data/styles/Tajawal/Tajawal-Medium.ttf new file mode 100644 index 0000000..5b7ec2b Binary files /dev/null and b/data/styles/Tajawal/Tajawal-Medium.ttf differ diff --git a/data/styles/Tajawal/Tajawal-Regular.ttf b/data/styles/Tajawal/Tajawal-Regular.ttf new file mode 100644 index 0000000..14394f9 Binary files /dev/null and b/data/styles/Tajawal/Tajawal-Regular.ttf differ diff --git a/data/styles/checkbox.css b/data/styles/checkbox.css new file mode 100644 index 0000000..3046f21 --- /dev/null +++ b/data/styles/checkbox.css @@ -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; +} + diff --git a/data/styles/color_picker.css b/data/styles/color_picker.css new file mode 100644 index 0000000..a5b510e --- /dev/null +++ b/data/styles/color_picker.css @@ -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; +} \ No newline at end of file diff --git a/data/styles/font.ttf b/data/styles/font.ttf new file mode 100644 index 0000000..199cf40 Binary files /dev/null and b/data/styles/font.ttf differ diff --git a/data/styles/range_input.css b/data/styles/range_input.css new file mode 100644 index 0000000..1d7369c --- /dev/null +++ b/data/styles/range_input.css @@ -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; +} \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 0000000..ff5597a --- /dev/null +++ b/index.html @@ -0,0 +1,142 @@ + + + + + + + + + + + + + + + + + + + + + + + + Chainreact + + +
+
+ + + + +
+ + Changelog + +
+ +
+
+
+ + \ No newline at end of file diff --git a/styles.css b/styles.css new file mode 100644 index 0000000..1ed1ecf --- /dev/null +++ b/styles.css @@ -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; +} \ No newline at end of file