init
This commit is contained in:
commit
8c889222ce
10 changed files with 4525 additions and 0 deletions
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/node_modules
|
||||
/db
|
||||
/.env
|
45
account.js
Normal file
45
account.js
Normal file
|
@ -0,0 +1,45 @@
|
|||
import { initDb } from './db.js';
|
||||
import fetch from 'node-fetch';
|
||||
|
||||
let getToken = async (token) => {
|
||||
let db = await initDb();
|
||||
|
||||
let id = '!nobody';
|
||||
|
||||
let username = await fetch("https://nbg.dervland.net/auth.php",
|
||||
{
|
||||
"method": "get",
|
||||
"headers": {
|
||||
"cookie": `token=${token}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
username = await username.text();
|
||||
|
||||
try {
|
||||
username = (username.length > 1) ? username : '';
|
||||
|
||||
if (username) id = username;
|
||||
} catch (err) {
|
||||
id = '!nobody';
|
||||
}
|
||||
|
||||
let userExists = await db.all('SELECT * FROM user WHERE username = ?', [
|
||||
id
|
||||
]);
|
||||
|
||||
if (userExists.length < 1) {
|
||||
await db.run('INSERT INTO user (username, roles, nickname) VALUES (?,?,?)', [
|
||||
id,
|
||||
0,
|
||||
id
|
||||
]);
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
export {
|
||||
getToken
|
||||
};
|
74
db.js
Normal file
74
db.js
Normal file
|
@ -0,0 +1,74 @@
|
|||
import sqlite3 from 'sqlite3'
|
||||
import { open } from 'sqlite'
|
||||
import { mkdir, access } from 'node:fs/promises';
|
||||
|
||||
let db;
|
||||
|
||||
const FILE_DIRS = [
|
||||
'/db',
|
||||
'/db/pfp'
|
||||
]
|
||||
|
||||
async function newDir(dir) {
|
||||
await access(newDir)
|
||||
.then(() => {})
|
||||
.catch(async () => await mkdir(dir, { recursive: true }));
|
||||
}
|
||||
|
||||
async function initFolders() {
|
||||
for (var i = 0; i < FILE_DIRS.length; i++) {
|
||||
await newDir( `${process.cwd()}/${FILE_DIRS[i]}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function initDb() {
|
||||
if (db) return db;
|
||||
|
||||
await initFolders();
|
||||
|
||||
db = await open({
|
||||
filename: `${process.cwd()}/db/main.db`,
|
||||
driver: sqlite3.Database
|
||||
});
|
||||
|
||||
await db.run('CREATE TABLE IF NOT EXISTS user ( \
|
||||
username TEXT, \
|
||||
roles INTEGER, \
|
||||
nickname TEXT \
|
||||
)');
|
||||
|
||||
await db.run('CREATE TABLE IF NOT EXISTS tile ( \
|
||||
username CHAR(64), \
|
||||
x INTEGER, \
|
||||
y INTEGER \
|
||||
)');
|
||||
|
||||
await db.run('CREATE TABLE IF NOT EXISTS block ( \
|
||||
status INTEGER, \
|
||||
x INTEGER, \
|
||||
y INTEGER \
|
||||
)');
|
||||
|
||||
await db.run('CREATE TABLE IF NOT EXISTS player ( \
|
||||
username CHAR(64), \
|
||||
score INTEGER, \
|
||||
out INTEGER, \
|
||||
color CHAR(64) \
|
||||
)');
|
||||
|
||||
let blocks = await db.all('SELECT * FROM block');
|
||||
|
||||
if (!blocks || blocks.length == 0) {
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (0,0,0)');
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (1,1,0)');
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (1,-1,0)');
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (1,0,1)');
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (1,0,-1)');
|
||||
}
|
||||
|
||||
return db;
|
||||
}
|
||||
|
||||
export {
|
||||
initDb
|
||||
};
|
312
gridwar.js
Normal file
312
gridwar.js
Normal file
|
@ -0,0 +1,312 @@
|
|||
import { initDb } from "./db.js";
|
||||
import { getToken } from './account.js';
|
||||
|
||||
let dat = {};
|
||||
|
||||
let cooldown = {};
|
||||
let lastTurn = '';
|
||||
|
||||
let users = {};
|
||||
|
||||
let initTile = async (x, y, username) => {
|
||||
let db = await initDb();
|
||||
|
||||
let data = new Array(25);
|
||||
|
||||
let blockData = await db.all('SELECT * FROM block WHERE x = ? AND y = ?', [
|
||||
x,
|
||||
y
|
||||
]);
|
||||
|
||||
let occupied = await db.all('SELECT * FROM tile WHERE username = ?', [
|
||||
username
|
||||
]);
|
||||
|
||||
let blockEntry = blockData[0];
|
||||
|
||||
if (!dat[`${x},${y}`]) {
|
||||
dat[`${x},${y}`] = {};
|
||||
}
|
||||
|
||||
dat[`${x},${y}`].type = blockEntry ? (blockEntry.status == 1 ? 'select' : 'claimed') : ''
|
||||
|
||||
if (username && blockEntry && blockEntry.status == 1 && (!occupied || occupied.length < 1)) {
|
||||
for (let i = 0; i < 25; i++) {
|
||||
data[i] = username;
|
||||
}
|
||||
|
||||
let vals = [
|
||||
[x + 1, y],
|
||||
[x, y + 1],
|
||||
[x - 1, y],
|
||||
[x, y - 1]
|
||||
];
|
||||
|
||||
await db.run('UPDATE block SET status = 0 WHERE x = ? AND y = ?', [
|
||||
x,
|
||||
y
|
||||
]);
|
||||
|
||||
for (let thing of vals) {
|
||||
await db.run('INSERT INTO block (status, x, y) VALUES (1,?,?)', [
|
||||
thing[0],
|
||||
thing[1]
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
} else if (!blockEntry || blockEntry.status == 1) {
|
||||
return false;
|
||||
} else {
|
||||
for (let i = 0; i < 25; i++) {
|
||||
data[i] = data[i] || '!nobody';
|
||||
}
|
||||
}
|
||||
|
||||
let table = await db.all('SELECT * FROM tile WHERE x < ? AND y < ? AND x > ? AND y > ?', [
|
||||
x * 5 + 5,
|
||||
y * 5 + 5,
|
||||
x * 5 - 1,
|
||||
y * 5 - 1,
|
||||
]);
|
||||
|
||||
for (let i in data) {
|
||||
let x2 = i % 5 + x * 5;
|
||||
let y2 = Math.floor(i / 5) + y * 5;
|
||||
|
||||
let tile = table.find(({ x, y }) => x == x2 && y == y2);
|
||||
|
||||
|
||||
if (tile) {
|
||||
data[i] = tile.username;
|
||||
} else {
|
||||
await db.run('DELETE FROM tile WHERE x = ? AND y = ?', [
|
||||
x2,
|
||||
y2
|
||||
]);
|
||||
await db.run('INSERT INTO tile (username, x, y) VALUES (?, ?, ?)', [
|
||||
data[i],
|
||||
x2,
|
||||
y2
|
||||
]);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dat[`${x},${y}`].data = data;
|
||||
|
||||
return dat;
|
||||
}
|
||||
|
||||
|
||||
(async () => {
|
||||
|
||||
let db = await initDb();
|
||||
|
||||
let blocks = await db.all('SELECT * FROM block');
|
||||
|
||||
for (let block of blocks) {
|
||||
initTile(block.x, block.y);
|
||||
}
|
||||
|
||||
let dataUser = await db.all('SELECT * FROM player');
|
||||
|
||||
for (let entry of dataUser) {
|
||||
dat[`_user_${entry.username}`] = entry;
|
||||
}
|
||||
})();
|
||||
|
||||
let socketHandler = async (socket, io) => {
|
||||
let user;
|
||||
|
||||
let db = await initDb();
|
||||
|
||||
socket.on('join', async ({ token }) => {
|
||||
user = await getToken(token);
|
||||
|
||||
if (user == '!nobody') {
|
||||
user = '';
|
||||
socket.emit('data','noauth');
|
||||
}
|
||||
|
||||
users[user] = true;
|
||||
|
||||
socket.emit('data', dat);
|
||||
|
||||
socket.emit('cooldown', cooldown[user]);
|
||||
socket.emit('turn',lastTurn);
|
||||
|
||||
let dataUser = await db.all('SELECT * FROM player WHERE username = ?', [
|
||||
user
|
||||
]);
|
||||
|
||||
if (dataUser && dataUser.length > 0) {
|
||||
dat[`_user_${user}`] = dataUser[0];
|
||||
} else {
|
||||
dat[`_user_${user}`] = {
|
||||
'username': user,
|
||||
'score': 0,
|
||||
'out': 0,
|
||||
'color': `hsl(${Math.random() * 360}deg,${Math.random() * 30 + 65}%,${Math.random() * 30 + 60}%)`
|
||||
};
|
||||
|
||||
await db.run('INSERT INTO player (username, score, out, color) VALUES (?,?,?,?)', [
|
||||
dat[`_user_${user}`].username,
|
||||
dat[`_user_${user}`].score,
|
||||
dat[`_user_${user}`].out,
|
||||
dat[`_user_${user}`].color
|
||||
]);
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
socket.on('color', async (color) => {
|
||||
|
||||
await db.run('UPDATE player SET color = ? WHERE username = ?', [
|
||||
color,
|
||||
user
|
||||
]);
|
||||
|
||||
dat[`_user_${user}`].color = color;
|
||||
|
||||
socket.emit('data', dat);
|
||||
})
|
||||
|
||||
socket.on('disconnect', () => {
|
||||
delete users[user];
|
||||
})
|
||||
|
||||
socket.on('claim', async ({ x, y }) => {
|
||||
if (!user) return;
|
||||
|
||||
x = x * 1;
|
||||
y = y * 1;
|
||||
|
||||
await initTile(x, y, user);
|
||||
|
||||
let blocks = await db.all('SELECT * FROM block');
|
||||
|
||||
for (let block of blocks) {
|
||||
await initTile(block.x, block.y);
|
||||
}
|
||||
|
||||
io.emit('data', dat);
|
||||
})
|
||||
|
||||
socket.on('conquer', async ({ x, y }) => {
|
||||
if (!user) return;
|
||||
|
||||
x = x * 1;
|
||||
y = y * 1;
|
||||
|
||||
let x2 = Math.floor(x / 5);
|
||||
let y2 = Math.floor(y / 5);
|
||||
|
||||
let x3 = x % 5;
|
||||
let y3 = Math.floor(y / 5);
|
||||
|
||||
let key = `${x2},${y2}`
|
||||
|
||||
if (!dat[key]) return;
|
||||
|
||||
let vals = [
|
||||
[x + 1, y],
|
||||
[x, y + 1],
|
||||
[x - 1, y],
|
||||
[x, y - 1]
|
||||
];
|
||||
|
||||
let canClaim = false;
|
||||
|
||||
for (let val of vals) {
|
||||
let isClaimed = await db.all('SELECT * FROM tile WHERE x = ? AND y = ?', [
|
||||
val[0],
|
||||
val[1]
|
||||
]);
|
||||
|
||||
console.log(isClaimed)
|
||||
|
||||
if (isClaimed && isClaimed.length > 0 && isClaimed[0].username == user) {
|
||||
canClaim = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!canClaim) return;
|
||||
|
||||
if (new Date() * 1 < cooldown[user] || lastTurn == user) return;
|
||||
|
||||
let userFactor = Math.min(Object.keys(users).length, 10);
|
||||
userFactor = Math.max(userFactor, 1);
|
||||
|
||||
cooldown[user] = (60000 / userFactor) + (new Date() * 1);
|
||||
lastTurn = user;
|
||||
|
||||
socket.emit('cooldown', cooldown[user]);
|
||||
io.emit('turn',lastTurn);
|
||||
|
||||
let formerOwner = await db.all('SELECT * FROM tile WHERE x = ? AND y = ?', [
|
||||
x,
|
||||
y
|
||||
]);
|
||||
|
||||
let formerOwnerStr = formerOwner[0].username;
|
||||
|
||||
formerOwner = formerOwner[0] ? `_user_${formerOwner[0].username}` : false;
|
||||
|
||||
await db.run('UPDATE tile SET username = ? WHERE x = ? AND y = ?', [
|
||||
user,
|
||||
x,
|
||||
y
|
||||
]);
|
||||
|
||||
dat[key].data[`${x3},${y3}`] = user;
|
||||
|
||||
await initTile(x2, y2);
|
||||
|
||||
io.emit('data', dat);
|
||||
|
||||
if (!dat[formerOwner]) {
|
||||
dat[formerOwner] = {
|
||||
'username': formerOwnerStr,
|
||||
'score': 0,
|
||||
'out': 0,
|
||||
'color': `hsl(${Math.random() * 360}deg,${Math.random() * 30 + 65}%,${Math.random() * 30 + 60}%)`
|
||||
};
|
||||
|
||||
await db.run('INSERT INTO player (username, score, out, color) VALUES (?,?,?,?)', [
|
||||
dat[formerOwner].username,
|
||||
dat[formerOwner].score,
|
||||
dat[formerOwner].out,
|
||||
dat[formerOwner].color
|
||||
]);
|
||||
}
|
||||
|
||||
let tCount = (await db.all('SELECT * FROM tile WHERE username = ?', [
|
||||
formerOwnerStr
|
||||
])).length;
|
||||
|
||||
if (tCount < 26) {
|
||||
dat[formerOwner].score--;
|
||||
} else {
|
||||
dat[formerOwner].score--;
|
||||
dat[`_user_${user}`].score++;
|
||||
}
|
||||
|
||||
await db.run('UPDATE player SET score = ? WHERE username = ?', [
|
||||
dat[`_user_${user}`].score,
|
||||
user
|
||||
]);
|
||||
|
||||
await db.run('UPDATE player SET score = ? WHERE username = ?', [
|
||||
dat[formerOwner].score,
|
||||
formerOwnerStr
|
||||
]);
|
||||
|
||||
console.log(dat[`_user_${user}`].score,dat[formerOwner].score);
|
||||
});
|
||||
}
|
||||
|
||||
export {
|
||||
socketHandler
|
||||
};
|
32
index.js
Normal file
32
index.js
Normal file
|
@ -0,0 +1,32 @@
|
|||
import express from 'express';
|
||||
|
||||
import { Server } from 'socket.io'
|
||||
import { createServer } from 'http';
|
||||
import { socketHandler } from './gridwar.js';
|
||||
|
||||
const app = express()
|
||||
const server = createServer(app);
|
||||
const io = new Server(server);
|
||||
const port = process.argv[2] || 3069;
|
||||
|
||||
app.use((req, res, next) => {
|
||||
let param = new URL(`https://gw.dervland.net/${req.originalUrl}`).searchParams.get('token');
|
||||
if (param) {
|
||||
res.cookie('token', param, {
|
||||
maxAge: 1000 * 60 * 60 * 24 * 7,
|
||||
path: '/'
|
||||
});
|
||||
}
|
||||
next();
|
||||
});
|
||||
app.use(express.static('./static'));
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
socketHandler(socket, io)
|
||||
})
|
||||
|
||||
server.listen(port, () => {
|
||||
console.log(`Web server listening on port ${port}`)
|
||||
})
|
||||
|
||||
|
3434
package-lock.json
generated
Normal file
3434
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
11
package.json
Normal file
11
package.json
Normal file
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"cookie-parser": "^1.4.6",
|
||||
"express": "^4.18.2",
|
||||
"http": "^0.0.1-security",
|
||||
"socket.io": "^4.7.1",
|
||||
"sqlite": "^5.0.1",
|
||||
"sqlite3": "^5.1.6"
|
||||
}
|
||||
}
|
291
static/gridwar.css
Normal file
291
static/gridwar.css
Normal file
|
@ -0,0 +1,291 @@
|
|||
:root {
|
||||
--dark-1: #2b2f36;
|
||||
--dark-2: #d8d8d8;
|
||||
|
||||
--light-1: #ffffff;
|
||||
--light-2: #f8f8f8;
|
||||
|
||||
--red-1: rgb(245, 108, 108);
|
||||
--purple-1: rgb(187, 112, 236);
|
||||
--blue-1: rgb(112, 169, 235);
|
||||
|
||||
--shadow-1: 0px 0px 2px 2px var(--dark-2);
|
||||
--border-1: solid var(--dark-2) 5px;
|
||||
|
||||
font-family: 'Noto Sans';
|
||||
}
|
||||
|
||||
body {
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
background: var(--light-1);
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--blue-1);
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
input:not([type='file']):not([type='checkbox']),
|
||||
textarea {
|
||||
border: 0;
|
||||
|
||||
border: solid var(--dark-2) 2px;
|
||||
border-radius: 5px;
|
||||
|
||||
font-size: 0.8rem;
|
||||
padding: 0.5rem;
|
||||
width: 200px;
|
||||
|
||||
margin: 10px;
|
||||
}
|
||||
|
||||
#content {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
|
||||
overflow-y: auto;
|
||||
|
||||
padding-top: 0px;
|
||||
}
|
||||
|
||||
.button {
|
||||
min-width: 150px;
|
||||
height: 30px;
|
||||
background: var(--red-1);
|
||||
color: var(--light-1);
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.button a {
|
||||
color: white;
|
||||
}
|
||||
|
||||
.area,
|
||||
input:not([type='file']),
|
||||
textarea {
|
||||
border: var(--border-1);
|
||||
}
|
||||
|
||||
textarea,
|
||||
.button,
|
||||
#content,
|
||||
.wrapper,
|
||||
.postie {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
input:not([type='file']),
|
||||
.area {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.area,
|
||||
input:not([type='file']),
|
||||
textarea,
|
||||
.button {
|
||||
border-radius: 10px;
|
||||
padding: 5px;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.header div {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
#content,
|
||||
.button {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#content {
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.area {
|
||||
width: 500px;
|
||||
min-height: 175px;
|
||||
}
|
||||
|
||||
.area,
|
||||
.button,
|
||||
.header {
|
||||
margin: 5px;
|
||||
}
|
||||
|
||||
.header.header-first>.post,
|
||||
.header.header-last>.post {
|
||||
width: min(500px, 90vw);
|
||||
}
|
||||
|
||||
.header {
|
||||
position: sticky;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.header.header-first {
|
||||
top: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header.header-last {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.header .menu,
|
||||
.dropdown {
|
||||
border: none;
|
||||
background: var(--blue-1);
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
border-radius: 10px;
|
||||
}
|
||||
|
||||
.header a {
|
||||
color: var(--light-1);
|
||||
height: 1.5em;
|
||||
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.menu {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
|
||||
.header span {
|
||||
color: var(--light-1);
|
||||
}
|
||||
|
||||
.dropdown {
|
||||
position: absolute;
|
||||
margin-top: 3em;
|
||||
margin-left: 0px;
|
||||
|
||||
background: var(--purple-1);
|
||||
color: inherit;
|
||||
|
||||
width: 10em;
|
||||
|
||||
text-align: center;
|
||||
justify-content: center;
|
||||
|
||||
opacity: 0;
|
||||
|
||||
font-size: 0.8em;
|
||||
height: 0.8em;
|
||||
|
||||
transition: 0.3s ease-in-out opacity;
|
||||
}
|
||||
|
||||
a:hover>.dropdown {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 2em;
|
||||
max-height: 2em;
|
||||
margin: 0.2em;
|
||||
}
|
||||
|
||||
#post-area {
|
||||
width: 500px;
|
||||
min-height: 200px;
|
||||
display: block;
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.left {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.hwrap,
|
||||
.area.postie {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.hwrap>span {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
form {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.pfp {
|
||||
background: linear-gradient(to top, rgba(255, 255, 255, 1), rgba(255, 255, 255, 0.7)), var(--img) center / cover
|
||||
}
|
||||
|
||||
#grid {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100vw;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
svg text, .header-first, .header-last {
|
||||
cursor: default;
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
svg text {
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
.info {
|
||||
top: 15px;
|
||||
left: 15px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
.info2 {
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
font-weight: bold;
|
||||
position: absolute;
|
||||
z-index: 100;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
svg text {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
input:not([type='file']):not([type='checkbox'])[type='color'] {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
}
|
||||
|
||||
.postie .icon {
|
||||
border-radius: 100%;
|
||||
height: 2em;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
#content > p {
|
||||
max-width: 600px;
|
||||
}
|
292
static/gridwar.js
Normal file
292
static/gridwar.js
Normal file
|
@ -0,0 +1,292 @@
|
|||
let area = document.querySelector('#grid');
|
||||
let group = document.querySelector('#group');
|
||||
|
||||
let mpos = [0, 0];
|
||||
let translate = "";
|
||||
let width = 0;
|
||||
let height = 0;
|
||||
let center = [0, 0];
|
||||
let lastPos = [0, 0];
|
||||
let isMouse = false;
|
||||
let zoom = 0.8;
|
||||
|
||||
genTrans();
|
||||
|
||||
function mouseMove(e) {
|
||||
lastPos = [e.clientX, e.clientY];
|
||||
if (!isMouse) return;
|
||||
mpos[0] += e.clientX - center[0];
|
||||
mpos[1] += e.clientY - center[1];
|
||||
center = lastPos;
|
||||
|
||||
genTrans();
|
||||
}
|
||||
|
||||
function mouseUp(e) {
|
||||
isMouse = false;
|
||||
}
|
||||
|
||||
function mouseDown(e) {
|
||||
isMouse = true;
|
||||
center = [e.clientX, e.clientY];
|
||||
}
|
||||
|
||||
function wheel(e) {
|
||||
mpos[0] -= lastPos[0];
|
||||
mpos[1] -= lastPos[1];
|
||||
|
||||
mpos[0] *= 1.005 ** -e.deltaY;
|
||||
mpos[1] *= 1.005 ** -e.deltaY;
|
||||
|
||||
mpos[0] += lastPos[0];
|
||||
mpos[1] += lastPos[1];
|
||||
|
||||
zoom *= 1.005 ** -e.deltaY;
|
||||
|
||||
genTrans();
|
||||
}
|
||||
|
||||
function genTrans() {
|
||||
group.setAttribute('transform', `translate(${mpos[0]},${mpos[1]}), scale(${zoom},${zoom})`);
|
||||
}
|
||||
|
||||
window.onwheel = wheel;
|
||||
window.onmousemove = mouseMove;
|
||||
window.onmouseup = mouseUp;
|
||||
window.onmousedown = mouseDown;
|
||||
window.ondblclick = doubleClick;
|
||||
|
||||
let tiles = {};
|
||||
let colors = {
|
||||
'!nobody': 'rgb(255,255,255)'
|
||||
};
|
||||
|
||||
let amounts = {
|
||||
'!nobody': -1
|
||||
};
|
||||
|
||||
let areas = {};
|
||||
|
||||
function doubleClick(e) {
|
||||
let data = e.target.id.split('|').pop();
|
||||
|
||||
data = data.split('_');
|
||||
|
||||
if (e.target.id.startsWith('grid-claimed')) {
|
||||
socket.emit('conquer', { x: data[0], y: data[1] })
|
||||
} else if (e.target.id.startsWith('grid-select')) {
|
||||
socket.emit('claim', { x: data[0], y: data[1] })
|
||||
}
|
||||
}
|
||||
|
||||
function getCookie(name) {
|
||||
const value = `; ${document.cookie}`;
|
||||
const parts = value.split(`; ${name}=`);
|
||||
if (parts.length === 2) return parts.pop().split(';').shift();
|
||||
}
|
||||
|
||||
function renderRect(x, y, h, w) {
|
||||
let elem = document.createElementNS("http://www.w3.org/2000/svg", 'rect');
|
||||
elem.setAttribute('x', x);
|
||||
elem.setAttribute('y', y);
|
||||
elem.setAttribute('width', w);
|
||||
elem.setAttribute('height', h);
|
||||
elem.setAttribute('fill', 'white');
|
||||
elem.setAttribute('stroke', 'black');
|
||||
elem.setAttribute('stroke-width', '3.5');
|
||||
|
||||
group.appendChild(elem);
|
||||
|
||||
return elem;
|
||||
}
|
||||
|
||||
function renderSelect(key, val) {
|
||||
let elem = renderRect(key[0] * 125, key[1] * 125, 125, 125);
|
||||
|
||||
elem.id = `grid-select|${key[0]}_${key[1]}`;
|
||||
}
|
||||
|
||||
function renderClaimed(key, val, data2) {
|
||||
for (let i in val.data) {
|
||||
if (i.startsWith('_')) continue;
|
||||
|
||||
let space = val.data[i];
|
||||
|
||||
let innerGroup = (space != '!nobody') ? document.querySelector(`g#${space}`) : false;
|
||||
|
||||
let userData = data2[`_user_${space}`];
|
||||
|
||||
colors[space] = userData ? userData.color : colors[space];
|
||||
|
||||
if (space == '!nobody') {
|
||||
colors[space] = 'rgb(255,255,255)';
|
||||
}
|
||||
|
||||
if (!colors[space]) {
|
||||
colors[space] = `hsl(${Math.random() * 360}deg,${Math.random() * 30 + 65}%,${Math.random() * 30 + 60}%)`
|
||||
}
|
||||
|
||||
if (!amounts[space]) {
|
||||
amounts[space] = 0;
|
||||
areas[space] = [];
|
||||
}
|
||||
|
||||
let x = i % 5;
|
||||
let y = Math.floor(i / 5);
|
||||
|
||||
if (areas[space]) {
|
||||
let x2 = key[0] * 125 + x * 25;
|
||||
let y2 = key[1] * 125 + y * 25;
|
||||
|
||||
let filter = areas[space].filter((a) => {
|
||||
return a.findIndex(([x, y]) => Math.abs(x2 - x) + Math.abs(y2 - y) < 2 * 25) != -1
|
||||
});
|
||||
|
||||
let noFilter = areas[space].filter((a) => {
|
||||
return a.findIndex(([x, y]) => Math.abs(x2 - x) + Math.abs(y2 - y) < 2 * 25) == -1
|
||||
});
|
||||
|
||||
areas[space] = [];
|
||||
|
||||
areas[space].push(...noFilter);
|
||||
filter = [filter.flat(1)];
|
||||
|
||||
areas[space].push(...(filter));
|
||||
|
||||
if (filter.length == 0) {
|
||||
areas[space].push([[x2, y2]]);
|
||||
} else {
|
||||
areas[space][areas[space].length - 1].push([x2, y2]);
|
||||
}
|
||||
}
|
||||
|
||||
amounts[space]++;
|
||||
|
||||
if (!innerGroup && space != '!nobody') {
|
||||
let elem = document.createElementNS("http://www.w3.org/2000/svg", 'g');
|
||||
elem.id = space;
|
||||
|
||||
group.appendChild(elem);
|
||||
|
||||
innerGroup = elem;
|
||||
}
|
||||
|
||||
let elem = renderRect(key[0] * 125 + x * 25, key[1] * 125 + y * 25, 25, 25);
|
||||
|
||||
if (innerGroup) innerGroup.appendChild(elem);
|
||||
|
||||
elem.setAttribute('fill', colors[space]);
|
||||
|
||||
elem.id = `grid-claimed|${key[0] * 5 + x}_${key[1] * 5 + y}`;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let biggestBox = (thing) => {
|
||||
let xVal = thing.map(x => Math.min(...x.map(y => y[0])));
|
||||
let yVal = thing.map(x => Math.min(...x.map(y => y[1])));
|
||||
|
||||
let wVal = thing.map(x => Math.max(...x.map(y => y[0])) - Math.min(...x.map(y => y[0])));
|
||||
let hVal = thing.map(x => Math.max(...x.map(y => y[1])) - Math.min(...x.map(y => y[1])));
|
||||
let sVal = thing.map(x => (Math.max(...x.map(y => y[0])) - Math.min(...x.map(y => y[0]))) * ((Math.max(...x.map(y => y[1])) - Math.min(...x.map(y => y[1])))));
|
||||
|
||||
let i = sVal.indexOf(Math.max(...sVal));
|
||||
|
||||
return {
|
||||
x: xVal[i],
|
||||
y: yVal[i],
|
||||
width: wVal[i] + 25,
|
||||
height: hVal[i] + 25
|
||||
}
|
||||
}
|
||||
|
||||
const socket = io(host);
|
||||
|
||||
socket.emit('area', { area: 'grid' });
|
||||
|
||||
|
||||
var token = getCookie('token');
|
||||
var noauth = false;
|
||||
|
||||
setTimeout(() => {
|
||||
if (!token) window.location.href = 'https://nbg.dervland.net/login.php?next=' + encodeURIComponent(window.location.href);
|
||||
if (token && window.location.search != '') window.location.href = '/';
|
||||
|
||||
socket.emit('join', { token });
|
||||
}, 800);
|
||||
|
||||
socket.on('data', (data) => {
|
||||
if (data == 'noauth') {
|
||||
noauth = true;
|
||||
}
|
||||
|
||||
group.innerHTML = '';
|
||||
for (let key in data) {
|
||||
let val = data[key];
|
||||
|
||||
let keyDecomp = key.split(',');
|
||||
|
||||
if (val.type == 'select') {
|
||||
renderSelect(keyDecomp, val);
|
||||
} else if (val.type == 'claimed') {
|
||||
renderClaimed(keyDecomp, val, data);
|
||||
}
|
||||
}
|
||||
|
||||
let groups = document.querySelectorAll(`g g`);
|
||||
|
||||
for (let group of groups) {
|
||||
let box = biggestBox(areas[group.id])
|
||||
|
||||
let id = group.id;
|
||||
|
||||
let elem = document.createElementNS("http://www.w3.org/2000/svg", 'text');
|
||||
elem.setAttribute("x", box.x + box.width / 2);
|
||||
elem.setAttribute("y", box.y + box.height / 2 - box.width / 4);
|
||||
elem.setAttribute("font-size", box.width / 8 + "px");
|
||||
elem.setAttribute("stroke", "white");
|
||||
elem.setAttribute("stroke-width", box.width / 35)
|
||||
elem.setAttribute("paint-order", "stroke")
|
||||
elem.setAttribute('text-anchor', 'middle')
|
||||
|
||||
let lines = [id, amounts[id]];
|
||||
|
||||
for (let line of lines) {
|
||||
let tspan = document.createElementNS("http://www.w3.org/2000/svg", 'tspan');
|
||||
tspan.textContent = line;
|
||||
tspan.setAttribute('x', box.x + box.width / 2);
|
||||
tspan.setAttribute('dy', box.width / 6);
|
||||
elem.appendChild(tspan);
|
||||
}
|
||||
|
||||
document.querySelector('svg > g').appendChild(elem);
|
||||
|
||||
amounts[id] = 0;
|
||||
}
|
||||
})
|
||||
|
||||
let cooldown = 0;
|
||||
let lastTurn = 'nobody';
|
||||
|
||||
socket.on('cooldown', (cooldownIn) => {
|
||||
cooldown = cooldownIn;
|
||||
})
|
||||
|
||||
socket.on('turn', (cooldownIn) => {
|
||||
lastTurn = cooldownIn;
|
||||
})
|
||||
|
||||
setInterval(() => {
|
||||
let distance = new Date(cooldown) - new Date();
|
||||
|
||||
if (distance < 0) {
|
||||
document.querySelector('.info').textContent = `The next turn is now, last turn was by ${lastTurn || 'nobody'}`;
|
||||
} else {
|
||||
document.querySelector('.info').textContent = `The next turn is in ${Math.floor(distance / 1000)} seconds, last turn was by ${lastTurn || 'nobody'}`;
|
||||
}
|
||||
|
||||
}, 100);
|
||||
|
||||
document.querySelector('#color').onchange = (e) => {
|
||||
socket.emit('color', e.target.value)
|
||||
}
|
31
static/index.html
Normal file
31
static/index.html
Normal file
|
@ -0,0 +1,31 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<link rel="icon" href="/img/icon.svg" />
|
||||
<link href='/gridwar.css' rel='stylesheet'>
|
||||
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap"
|
||||
rel="stylesheet">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<span class='info'>
|
||||
Your cooldown
|
||||
</span>
|
||||
<span class='info2'>
|
||||
<div>Your grid color</div>
|
||||
<input type="color" id="color" value="#e66465">
|
||||
</span>
|
||||
<svg id="grid">
|
||||
<g transform="" id='group'>
|
||||
</g>
|
||||
</svg>
|
||||
<script>
|
||||
let host = '/';
|
||||
</script>
|
||||
<script src="./socket.io/socket.io.js"></script>
|
||||
<script src='/gridwar.js'></script>
|
||||
</body>
|
Loading…
Reference in a new issue