From 9f1bf7eb4d834dc31ba39b12fe7884f74d717dde Mon Sep 17 00:00:00 2001 From: A Bass Date: Wed, 20 Apr 2022 23:23:51 -0400 Subject: [PATCH] Good enough for now --- Assets/Sheet.xcf | Bin 102626 -> 103464 bytes index.js | 183 ++++++++++++++++++++++++++++++++++++++++------- net.js | 0 tileinfo.js | 18 +++++ util.js | 6 +- worldgen.js | 6 +- www/index.html | 18 +++-- www/script.js | 116 +++++++++++++++++++++--------- www/sheet.png | Bin 7422 -> 7482 bytes www/style.css | 9 +++ 10 files changed, 287 insertions(+), 69 deletions(-) create mode 100644 net.js create mode 100644 tileinfo.js diff --git a/Assets/Sheet.xcf b/Assets/Sheet.xcf index a2aa2457e6cd8b2c6cb2a807e42be7d37bc90e47..f1a8c0e7fab09657233de1184062c5f52ee07275 100644 GIT binary patch delta 2510 zcmbu9e@t6d6vyv*Erph0Tglo1Gf>@zl|^XnQhs!_OcziX8v~{ohwBP;Fb53U0kN?Y z(aayq7`F+@Gz(~g)tKmVlNC*)>1H%>nIRGl6623$8vihH%NFAw>hr#P-dkcaCs~`E z`?=?PZqGgM-t*q2IYTmOm^uaZXI`^Ge3*z|&aX&gAF}K?a+4K#-!#&-9=YW@vRYkN zw*mdFX=K2MYz!bD2_jp5LhihTbnmXgN$?M3?>opowXj2Xpg(dE8NI+Bmx=%;=~Y9q zq?@I#a+{=EU`bu0PRa$YmcU&?%-zC8zr=csCV)5{G?h5t#2jVMA;mbx$13zq=aI!P zF=kq^!D(d=aL_Xqc9Y{vj1fYIHWahVj;b8kC~7Eg;M>`ui#=T4qKA2*Rh$zgRIISi zD>@BOB}8|N5Zyhj!#%Hud?9+vh3NIOO7DIN-VkEVM?$Rmns)ozSy8P;f?5&q?GXXr z0PFUJCHP8+^;hYS^>#4QO79^S_J0Ggiv}7>oDrBx7bW`rv!fqv9(ButuE$hJtx6+A+}kB*mf^l*S1N5 zoz&l6;vB(^f@+TmPdRqAkamjky`Qq@+hb<1h)ki)B2(xg<_V4JAkD=>`f)f9mW3X^ z!hQ&!)WL+R=SuyjMWFvIoA2+_LD`MKfKLgS20CyXLRjh0yp9h`P|Kw?ssZPnyMiN; z-k!3S;CSzFS!i%*T=|cr4E&eFElAM;)Ct=y^ZR?Wl{_V^kX>ziBg0 zZ67^q(fpglxD>rd(}5V5R%3R;S9K{C*Czi(u{6V@|7&)_*UH9Qno;JNxDf(Jh4GJt z;^l7(h7LY8JQyA5p%z1eJ{k7WRKy%V@`d(jH~vA0dK{y8$dEuE5*D!*66yy>@zh;J zV9YH*o>f0Q$4;Stau0H*2|0T=GWi{ei`jdVC@fq?W@5UVJjxsy_MlPf%}t0@<+*xmS;z+ccXwO}`st+^5YW8e}y^XidaR zXV$l|@+>bsn})G@R^548mnxNKR>t8$Ps0N)qBfnRd-ALn*O81V#KqLM$XC3@rMAelu`(JB3bJll&b7Z+9~$N0h&GAxXba;f>t1Gxq2)DSTBqeVaRY!)rHhHAejy22!U!0=VPzZG1a05?n{ky z&sihgG@aJvhrMDr}2HG2sdCBfoig2l(rnmzQDMNMFaiOZQv_{uaZ zP_P~&_UtXsTq5`}!Vvc*s}eQG9wf6Rf(_;fS5`aHo=n@bCgqSP_0O(kJ+hl=bM_Yk z6_SwK%7ok=+LK#AU|kYiQGy!{Ni@mzj=-8U?^dz`x0Y(%dSZ8AM`nX>Xv=Cn6{wYp zhx)%9A@H=JerlYyc^V@jcw;pmJV@RI*28O|7H=H~!%Vp4R}kME6MU<5)pv)3OQN7; zCqP5|19ZS&&OwY+P@TpKs-4tXeJz}ysfh!^x046__k0WgqBb&caSj&|w9tejwB0qh z?a(pKa-d^c%A`8cnXHFo?4k6F|LN?%kD)e828+w{tUuL8!llx^n_v|zmAiG~e223dVjTRgK cy0w@tk1oaYpZe4Jxwkf&_cJY8FdusV0o;!IeE obj.id != this.id); + while (playerList > 1) { + for (let i = 1; i < playerList.length-1; i++) { + if (playerList[util.randomNumber(0,i)].gold < playerList[i].gold) { + delete playerList[i]; + } + } + playerlist = playerList.filter(obj => obj != null) + } + var player2 = playerList[0] + this.transfer(player2) + } + sendMap(client) + game.removePlayer(this.id); + } + + transfer(player2) { + player2.gold += this.gold + player2.power += this.power + for(let x = 0; x < game.gridSize[0]; x++) { + for(let y = 0; y < game.gridSize[1]; y++) { + if (game.world.map[x][y].owner == this.id) { + game.world.map[x][y].owner = player2.id + game.world.map[x][y].color = player2.color + } + } + } + + for (i = 0; i < this.buildings.length; i++) { + player2.buildings[i] += this.buildings[i] + } + + } + + + +} +class World { + constructor(gridSize, perlinScale) { + console.log(gridSize) + this.map = worldgen.generateWorld(gridSize, perlinScale) + } + + // Determine what method of clicking is to be used by player + click(structure, x, y, client){ + const player = game.getPlayerByID(client.id); + //Build Mode + if (this.map[x][y].owner == player.id) { + this.placeStructure(structure, x, y, player, client) + } + //Claim Mode + if (this.map[x][y].owner == null) { + this.claimLand(x, y, player, client) + } + + if (this.map[x][y].owner != null && this.map[x][y].owner != player.id) { + //Attack Mode + } + } + + // Function for placing buildings on land + placeStructure(structure, x, y, player, client) { + // Get info for the structure desired to be built from databank + const strucInfo = data.structureTypes[structure] + // Get the tile object the structure is to be built upon + const tileOfInterest = this.map[x][y]; + // get info for the tile the structure is to be built upon from databank + const tileInfo = data.tileTypes[tileOfInterest.type] + const cost = strucInfo.buildPrice + tileInfo.buildPrice; + + if ( + // Check if the structure is valid to be built + strucInfo.buildable == true + // Check if player is still under building limit for structure + && player.buildings[structure] < strucInfo.maxQuantity + // Ensure no other builings are already built in this tile + && tileOfInterest.structure == 0 + // Check to see if the structure can be built on the type of tile + && strucInfo.buildableOn.includes(tileOfInterest.type) + // Check if player has enough gold to build this + && player.gold > cost + ) { + // Update tile on map + this.map[x][y].structure = structure + + // Increment building count for this structure + player.buildings[structure] ++; + + // Charge player for cost of structure + player.gold -= cost; + + sendMap(client) + } + } + + claimLand(x, y, player, client) { + const tileOfInterest = this.map[x][y]; + + if (data.structureTypes[tileOfInterest.structure].claimable == true) { + this.map[x][y].owner = player.id + this.map[x][y].color = player.color + sendMap(client) + } + } + + +} + +function sendMap(client) { + client.broadcast.emit('sync',{world: game.world.map}) + client.emit('sync',{world: game.world.map}) } class Game { - constructor() { + constructor(gridSize) { this.players = []; - this.world = worldgen.generateWorld(gridSize, perlinScale); + this.world = new World(gridSize, perlinScale); + this.gridSize = gridSize } - addPlayer(id) { + addPlayer(id, name) { var color switch(util.randomNumber(1,4)) { case 1: @@ -37,8 +166,9 @@ class Game { color = "yellow"; break; } - const player = new Player(id, color); + const player = new Player(id, color, name); this.players.push(player); + console.log(player.name) } removePlayer(id) { @@ -47,14 +177,12 @@ class Game { } getPlayerByID(id) { - console.log(this.players) - console.log(id) const player = this.players.filter(obj => obj.id == id)[0] return player } } -var game = new Game(); +var game = new Game([18, 18]); var app = express() //Static resources server @@ -66,35 +194,38 @@ app.use(express.static(__dirname + '/www/'));var server = app.listen(8082, funct var io = require('socket.io')(server);/* Connection events */ io.on('connection', function(client) { console.log('User connected'); + client.emit('gameVars', {gridSize: game.gridSize, world: game.world.map}) + + client.on('disconnect', function(){ console.log(client.id + ' disconnected.') - game.removePlayer(client.id) - client.broadcast.emit('playerList', game.players) + game.getPlayerByID(client.id).kill(client) + // client.broadcast.emit('playerList', game.players) }) - client.on('joinGame', function(tank){ - game.addPlayer(client.id); + client.on('joinGame', function(data){ + console.log(game.players) console.log(client.id + ' joined the game'); - client.emit('gameVars', {gridSize: gridSize, world: game.world}) - client.broadcast.emit('playerList', game.players) - client.emit('playerList', game.players) + game.addPlayer(client.id, data.name); + // client.broadcast.emit('playerList', game.players) + // client.emit('playerList', game.players) }) client.on('leaveGame', function(tank){ console.log(client.id + ' disconnected.') - game.removePlayer(client.id) - client.broadcast.emit('playerList', game.players) + // game.getPlayerByID(cliend.id).kill() + game.getPlayerByID(client.id).kill(client) + // client.broadcast.emit('playerList', game.players) }) client.on('clickCanvas', function(data){ - const xu = data.tilePosition[0] - const yu = data.tilePosition[1] - game.world[xu][yu].structure = data.structure - game.world[xu][yu].owner = game.getPlayerByID(client.id).color; - // console.log(world[xu][yu].owner = game.getPlayerbyID(client.id)) - - client.broadcast.emit('sync',{world: game.world}) - client.emit('sync',{world: game.world}) + const xu = util.clamp(data.tilePosition[0], 0, game.gridSize[0]) + const yu = util.clamp(data.tilePosition[1], 0, game.gridSize[1]) + // game.world.map[xu][yu].structure = data.structure + game.world.click(data.structure, xu, yu, client) + // game.world.map[xu][yu].owner = game.getPlayerByID(client.id).color; + // client.broadcast.emit('sync',{world: game.world.map}) + // client.emit('sync',{world: game.world.map}) }) diff --git a/net.js b/net.js new file mode 100644 index 0000000..e69de29 diff --git a/tileinfo.js b/tileinfo.js new file mode 100644 index 0000000..d59a2fd --- /dev/null +++ b/tileinfo.js @@ -0,0 +1,18 @@ +const tileTypes = +[ +{name: "Grass", textureID: 0, buildPrice: 20, capturePrice:10, capturePower: 10, captureTime: 2, sellPrice:5, recapturePrice: 15, recapturePower: 100, recaptureTime: 10}, +{name: "Hills", textureID: 1, buildPrice: 20, capturePrice:10, capturePower: 10, captureTime: 2, sellPrice:5, recapturePrice: 15, recapturePower: 100, recaptureTime: 10}, +{name: "Sea", textureID: 2, buildPrice: 20, capturePrice:10, capturePower: 10, captureTime: 2, sellPrice:5, recapturePrice: 15, recapturePower: 100, recaptureTime: 10} +]; + +const structureTypes = +[ +{name: "Air", textureID:null, claimable: true, buildable: false, destroyable: false, maxQuantity:null, buildPrice: null, sellPrice: null, buildableOn: null}, +{name: "Rock", textureID:4, claimable: false, buildable: false, destroyable: false, maxQuantity:null, buildPrice: null, sellPrice: null, buildableOn: [0,1]}, +{name: "Town", textureID:32, claimable: false, buildable: true, destroyable: true, maxQuantity:10, buildPrice: 10, sellPrice: 5, buildableOn: [0,2]}, +{name: "City", textureID:33, claimable: false, buildable: true, destroyable: true, maxQuantity:10, buildPrice: 20, sellPrice: 10, buildableOn: [0,2]}, +{name: "Capitol", textureID:34, claimable: false, buildable: true, destroyable: false, maxQuantity:1, buildPrice: 30, sellPrice: 20, buildableOn: [0,2]}, +] + + +module.exports = {tileTypes, structureTypes} diff --git a/util.js b/util.js index b3f9e1a..102b476 100644 --- a/util.js +++ b/util.js @@ -1,5 +1,7 @@ function randomNumber(min, max) { return Math.floor(Math.random() * (max - min) + min); } - -module.exports = {randomNumber} +function clamp(number, min, max) { + return Math.max(min, Math.min(number, max)); +} +module.exports = {randomNumber, clamp} diff --git a/worldgen.js b/worldgen.js index e41ef1e..e53581b 100644 --- a/worldgen.js +++ b/worldgen.js @@ -7,7 +7,7 @@ function generateWorld(gridSize, perlinScale){ let tempWorld = Array.from(Array(gridSize[0]), () => new Array(gridSize[1])); for (x = 0; x < gridSize[0]; x++){ for (y = 0; y < gridSize[1]; y++){ - tempWorld[x][y] = {type: 0, structure: 0, owner: null} + tempWorld[x][y] = {type: 0, structure: 0, color: null, owner: null} } } @@ -32,9 +32,9 @@ function generateWorld(gridSize, perlinScale){ y = util.randomNumber(0,gridSize[1]); if (tempWorld[x][y].type != 1) { i++; - tempWorld[x][y].structure = 1 - } + tempWorld[x][y].structure = util.randomNumber(1,4) } +} return tempWorld; } module.exports = { generateWorld }; diff --git a/www/index.html b/www/index.html index 7bf2e98..1369abb 100644 --- a/www/index.html +++ b/www/index.html @@ -7,22 +7,28 @@ -

Uber Secret Project

-
- + + + diff --git a/www/script.js b/www/script.js index 80a780b..c5c45a9 100644 --- a/www/script.js +++ b/www/script.js @@ -3,19 +3,34 @@ const tileSize = 24; var gridSize; var cash = 99; var world +var tiles = [] + var canvas = document.getElementById('canvas'); var hud = document.getElementById('hud'); var ctx = canvas.getContext('2d'); var hctx = hud.getContext('2d'); var socket = io.connect('http://127.0.0.1:8082/'); -function joinGame(socket){ - socket.emit('joinGame', {}); +function joinGame(socket, room, nick){ + socket.emit('joinGame', {room, nick}); } function leaveGame(socket){ socket.emit('leaveGame', {}); } -joinGame(socket); + + + + +class Cursor { + constructor(){ + this.x = 0 + this.y = 0 + this.xold = 0 + this.yold = 0 + } + +} +var cursor = new Cursor(); window.onbeforeunload = function(){ socket.disconnect(); @@ -38,20 +53,43 @@ socket.on('sync', function (sync){ render() }) -window.onload = function () { +function loadSprites(){ + var spriteJank = document.getElementById('spriteJank'); + var ctxj = spriteJank.getContext('2d'); + var spriteSheet = new Image(); - // GET THE IMAGE. - var city = new Image(); - city.src = 'img/city.png'; - var rock = new Image(); - rock.src = 'img/rock.png'; - var grass = new Image(); - grass.src = 'img/grass.png' - var sea = new Image(); - sea.src = 'img/sea.png' - var hills = new Image(); - hills.src = 'img/hills.png' - tiles = [grass,hills,sea,rock,city]; + spriteSheet.src = '/sheet.png' + spriteSheet.onload = function() { + + + for (y = 0; y < 16; y++) { + for (x = 0; x < 16; x++) { + ctxj.drawImage(spriteSheet, -x*24,-y*24) + var tmp = new Image(); + tmp.src = spriteJank.toDataURL(); + ctxj.clearRect(0, 0, 24, 24); + tiles.push(tmp) + } + } + } +} + +function submit(event) { + document.getElementById('menu').style = "display: none;" + document.getElementById('container').style = "" + const room = document.getElementById("room").value + event.preventDefault(); + joinGame(socket, {room: room, name: "bob"}) +} + + + +window.onload = function () { + const form = document.getElementById('form'); + + form.addEventListener('submit', submit); + +loadSprites() @@ -59,7 +97,9 @@ window.onload = function () { function fill_canvas() { // CREATE CANVAS CONTEXT. - + hud.addEventListener('mousemove', e => { + mouseMoved(e) + }); hud.addEventListener("mousedown", function(e) { getMousePosition(canvas, e); @@ -70,7 +110,6 @@ function fill_canvas() { hud.height = canvas.height - render() updateUI(); } @@ -103,22 +142,26 @@ function render() { // DRAW THE IMAGE TO THE CANVAS. // Draw Structures switch (world[x][y].structure) { case (1): - ctx.drawImage(tiles[3], xu,yu) + ctx.drawImage(tiles[4], xu,yu) break; - case (2): - ctx.drawImage(tiles[4], xu,yu) + ctx.drawImage(tiles[32], xu,yu) + break; + case (3): + ctx.drawImage(tiles[33], xu,yu) + break; + case (4): + ctx.drawImage(tiles[34], xu,yu) break; - } // Draw Property overlays - if (world[x][y].owner != null){ + if (world[x][y].color != null){ ctx.beginPath(); - ctx.fillStyle = world[x][y].owner; + ctx.fillStyle = world[x][y].color; ctx.rect(xu, yu, tileSize, tileSize) - ctx.globalAlpha = 0.5; + ctx.globalAlpha = 0.6; ctx.fill() ctx.globalAlpha = 1; } @@ -136,18 +179,27 @@ function getMousePosition(canvas, event) { const rect = canvas.getBoundingClientRect(); const x = event.clientX - rect.left; const y = event.clientY - rect.top; - const xu = Math.floor(x/tileSize) - const yu = Math.floor(y/tileSize) + // const xu = Math.floor(x/tileSize) + // const yu = Math.floor(y/tileSize) + const xu = cursor.x + const yu = cursor.y // Convert canvas coordinates to usable coordinates within the coordinate system console.log("Click!") console.log(xu,yu) socket.emit('clickCanvas', {tilePosition: [xu,yu], structure: 2}); - // if (tile.type != 1 && tile.structure != 1 && cash > 10) { - // tile.structure = 2 - // cash = cash - 10; - - // } } +function mouseMoved(event) { + const x = Math.floor(event.offsetX/tileSize) + const y = Math.floor(event.offsetY/tileSize) + if (cursor.xold != x || cursor.yold != y) { + cursor.x = x + cursor.y = y + hctx.clearRect(cursor.xold*tileSize, cursor.yold*tileSize, tileSize, tileSize) + hctx.drawImage(tiles[80], x*tileSize,y*tileSize) + cursor.xold = x + cursor.yold = y +} +} diff --git a/www/sheet.png b/www/sheet.png index 36a07914ef8ba5e9788380a08006242906bb24f3..d014407b92c5607dd44b1181762bb4e8c11722d2 100644 GIT binary patch literal 7482 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 R3CJ%1cFXu?=?%xoe*