all repos — uStrat @ 41d628ca8f20b24fa236986b8097d3c94fdbc31e

simple turn-based strategy game inspired by uCity, Super Robot Wars, C&C, Fire Emblem

Merge branch 'newAI' into 'master'

New ai

See merge request nilix/ustrat!1
Derek Stevens drkste@zoho.com
commit

41d628ca8f20b24fa236986b8097d3c94fdbc31e

parent

e194fb61f11506a1bbf75a9af135466a626f94bb

M AI.jsAI.js

@@ -1,4 +1,7 @@

-AI = {}; +// AI.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains AI logic (duh!) + +window.AI = {}; AI.animCounter = 0; AI.fCounter = 0; AI.Q = {}

@@ -150,7 +153,7 @@ s = i;

} } if (s > 0) - return teams.p1.structs[u]; + return teams.p1.structs[s]; else return teams.p1.structs[0];

@@ -174,7 +177,7 @@ for (x = 0; x < teams.cpu.units.length; x++)

{ if ( !isEmptyObject(teams.cpu.units[x].target) && teams.cpu.units[x].target.x == teams.cpu.structs[i].x && - teams.cpu.units[y].target.y == teams.cpu.structs[i].y) + teams.cpu.units[x].target.y == teams.cpu.structs[i].y) { skip = true; break;

@@ -193,7 +196,7 @@ s = i;

} } if (s > 0) - return teams.cpu.structs[u]; + return teams.cpu.structs[s]; else return teams.cpu.structs[0];

@@ -700,7 +703,7 @@ else if (AI.countStructs("cpu factory") >= AI.countStructs("cpu armory"))

{ buildArmory(); } - else if (AI.rand(4) < 2) + else if (AI.rand(4) < 1) { if (AI.countStructs("cpu factory") < AI.countStructs("cpu armory")) {

@@ -812,6 +815,17 @@ }

} } +AI.targetReset = function(target) +{ + for (var i = 0; i < teams.cpu.units.length; i++) + { + if (teams.cpu.units[i].target.x == target.x && + teams.cpu.units[i].target.y == target.y) + { + teams.cpu.units[i].attentionSpan = 0; + } + } +} AI.turnStart = function() {
M Attack.jsAttack.js

@@ -1,3 +1,7 @@

+// Attack.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains Attack procedures and some AI code related to them + + window.attack = {}; attack.pattern = {}; attack.rotations = [ 0 ];

@@ -22,19 +26,50 @@ }

attack.cpuInit = function() { - gameState.flow = "cpuAttackReady"; + if (AI.focus >= teams.cpu.units.length || isEmptyObject(teams.cpu.units[AI.focus].equipment)) + { + AI.focus = 0; + endPhase(); + } attack.src = teams.cpu.units[AI.focus]; attack.pattern = attack.src.equipment.pattern; if (attack.src.mode != "defend") { - mapCursor.x = attack.src.target.x; - mapCursor.y = attack.src.target.y; + if (dist(attack.src.target, attack.src) <= attack.src.equipment.range) + { + mapCursor.x = attack.src.target.x; + mapCursor.y = attack.src.target.y; + } + else + { + AI.focus++; + return; + } } else { // search for closest enemy unit in range, target it // if nothing in range, do nothing + var i, min, working; + min = 99999; + working = 0; + for (i = 0; i < teams.p1.units.length; i++) + { + working = dist(attack.src, teams.p1.units[i]); + if (working <= min && dist(teams.p1.units[i], attack.src) <= attack.src.range) + { + min = working; + mapCursor.x = taems.p1.units[i].x; + mapCursor.y = teams.p1.units[i].y; + } + } + if (min == 99999) + { + AI.focus++; + return; + } } + gameState.flow = "cpuAttackReady"; } attack.cancel = function()
M Engine.jsEngine.js

@@ -1,3 +1,9 @@

+// Engine.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains high level engine code and utility functions; +// aside from utility functions and the state machine known as loop(), +// we mostly have wrapper functions that call more complex +// functions from other files + window.gameState = {}; window.controller = {}; window.gameOverWindow = new Image();

@@ -132,6 +138,7 @@ attack.listen();

map.draw(); attack.drawPattern(); mapCursor.draw(); + hud.draw(); } function attackAnim()

@@ -145,7 +152,7 @@ {

switch (gameState.phase) { case "cpu": - gameState.flow = "cpuAttackReady" + gameState.flow = "cpuAttackInit"; break; case "p1": gameState.flow = "freeLook";

@@ -187,7 +194,8 @@ teams.cpu.units[AI.focus].target = {};

} if (isEmptyObject(teams.cpu.units[AI.focus].equipment)) { - AI.assignTarget(teams.cpu.units[AI.focus], AI.getClosestArmory(teams.cpu.units[AI.focus]), "defend"); + teams.cpu.units[AI.focus].target = AI.getClosestArmory(teams.cpu.units[AI.focus]); + teams.cpu.units[AI.focus].mode = "defend"; } else if (isEmptyObject(teams.cpu.units[AI.focus].target)) {

@@ -230,10 +238,15 @@ {

AI.focus = 0; AI.manageEquipment(); if (!isEmptyObject(teams.cpu.units[AI.focus].equipment)) + { + mapCursor.x = teams.cpu.units[AI.focus].x; + mapCursor.y = teams.cpu.units[AI.focus].y; gameState.flow = "cpuAttackInit"; - return; + return; + } + else endPhase(); } - gameState.flow = "cpuGenTargets"; + else gameState.flow = "cpuGenTargets"; } function cpuAttackInit()
M Equipment.jsEquipment.js

@@ -1,3 +1,6 @@

+// Equipment.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains all functions directly related to equipment + function equipment() { this.pattern = new Array(5);
M Keyboard.jsKeyboard.js

@@ -1,3 +1,6 @@

+// Keyboard.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains keybinds! + document.addEventListener("keydown", pressHandler, false); document.addEventListener("keyup", releaseHandler, false);
M Map.jsMap.js

@@ -1,3 +1,6 @@

+// Map.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains map, camera, and HUD data and functions + window.map = {}; map.sz = 64; map.gfx = {};

@@ -9,8 +12,12 @@ camera.y = 0;

window.hud = {}; hud.gfx = {}; +hud.gfx.statusWindow = new Image(); +hud.gfx.statusWindow.src = "assets/ui/statusWindow.png"; hud.gfx.terrainWindow = new Image(); hud.gfx.terrainWindow.src = "assets/ui/terrainWindow.png"; +hud.gfx.unitWindow = new Image(); +hud.gfx.unitWindow.src = "assets/ui/unitWindow.png"; hud.gfx.structWindow = new Image(); hud.gfx.structWindow.src = "assets/ui/structWindow.png"; hud.gfx.cycleModes = new Array(4);

@@ -58,17 +65,18 @@ screen.fillText(currentCell.type, 10, 20);

screen.fillText("def+" + currentCell.defMod, 10, 30); screen.fillText("agi-" + currentCell.agiMod, 10, 40); - screen.fillText(gameState.phase + " phase", 240, 10); - screen.fillText("ruin: " + gameState.ruin, 240, 20); - screen.fillText("E: " + teams.p1.energy + "/" + teams.cpu.energy, 240, 30); - screen.fillText("S: " + teams.p1.structs.length + "/" + teams.cpu.structs.length, 240, 40); - screen.fillText("U: " + teams.p1.units.length + "/" + teams.cpu.units.length, 240, 50); + screen.drawImage(hud.gfx.statusWindow, 230, 5); + screen.fillText(gameState.phase + " phase", 235, 10); + screen.fillText("ruin: " + gameState.ruin, 235, 20); + screen.fillText("E: " + teams.p1.energy + "/" + teams.cpu.energy, 235, 30); + screen.fillText("S: " + teams.p1.structs.length + "/" + teams.cpu.structs.length, 235, 40); + screen.fillText("U: " + teams.p1.units.length + "/" + teams.cpu.units.length, 235, 50); screen.drawImage(hud.gfx.cycleModes[mapCursor.cycleMode], 85, 5); if (!isEmptyObject(currentCell.structure)) { -// screen.drawImage(hud.gfx.structWindow, 5, 135); + screen.drawImage(hud.gfx.structWindow, 5, 135); screen.fillText(currentCell.structure.name, 10, 140); screen.fillText("HP: " + currentCell.structure.hp + "/" + currentCell.structure.maxHP, 10, 150); screen.fillText("E: " + currentCell.structure.energy + "@" + currentCell.structure.rate, 10, 160);

@@ -76,6 +84,7 @@ }

if (!isEmptyObject(currentCell.unit)) { + screen.drawImage(hud.gfx.unitWindow, 5, 55); screen.fillText(currentCell.unit.name, 10, 60); screen.fillText("HP: " + currentCell.unit.hp + "/" + currentCell.unit.maxHP, 10, 70); screen.fillText("pow: " + currentCell.unit.pow, 10, 80);

@@ -235,6 +244,7 @@ case "forest":

this.changeCell(self, "plain"); break; case "city": + AI.targetReset(self); this.changeCell(self, "ruins"); break; default:
M MiniMap.jsMiniMap.js

@@ -1,3 +1,6 @@

+// MiniMap.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains the mini-map data, listening, and drawing procedure + window.minimap = {}; minimap.gfx = {}; minimap.gfx.bg = new Image();
M Movement.jsMovement.js

@@ -1,3 +1,8 @@

+// Movement.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains data and functions related to Unit +// movement including player plotting of paths and movement +// animation + window.movement = {}; movement.chain = new Array(); movement.unit = {};
A README.md

@@ -0,0 +1,42 @@

+# uStrat + +uStrat is a strategy game developed primarily for mobile phones. It is inspired by games like uCity, Fire Emblem, and Super Robot Wars. uStrat is written in Javascript using HTML5 Canvas and a whole lotta elbow grease. + +## Gameplay + +The map is randomly generated at the beginning of the game. P1 and CPU headquarters are spawned in opposite quadrants of the map. The goal is to build your headquarters up with power generation, factories, and armories (Structures), and build an army of mecha soldiers (Units) equipped to defend the Cities and destroy the enemy's HQ. The CPU team will be doing the same, but aiming to destroy the Cities. + +Resource management is centered around Energy. Each power generation Structure adds a certain amount of Energy to your team's pool, up to a maximum defined by the sum of the power generation Structures. Building Units, Structures, and Equipment costs energy. + +Each Unit has its own strengths and weaknesses; Same goes for the equipment. You can mix and match units and equipment to come up with an arsenal that best matches your strategy. + +The game ends when too many cities are destroyed or either HQ is destroyed. + +## Controls + +uStrat can be controlled by keyboard (desktop/laptop) or touch (mobile). The game will automatically detect a mobile platform and render the touch controls when necessary. The controls are as follows: + +### touch + +* `d-Pad (left cross)`: movement +* `pause (center button)`: show/hide mini-map +* `face buttons (right cross)`: + * `Y (right button)`: select tile/menu option + * `X (bottom button)`: cancel selection or operation + * `Z (top button)`: reset movement path; change cycle class; rotate attack pattern + * `W (left button)`: cycle through current cycle class; rotate attack pattern + +### keyboard + +* `arrow keys`: movement +* `spacebar`: show/hide mini-map +* `x`: select tile/menu option +* `z`: cancel selection or operation +* `s`: reset movement path; change cycle class; rotate attack pattern +* `a`: cycle through current cycle class; rotate attack pattern + +## Development + +* currently finishing the AI and polishing up some UI +* there is a lot of improvement to be made on the AI's basic pathfinding. more will be done with this later after the game is more or less playable +* probably a million and one bugs. please do report them and/or contribute fixes!
M Struct.jsStruct.js

@@ -1,3 +1,8 @@

+// Struct.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains everything related to structures, +// including constructors, factories, and methods on structures +// (ie damage and destruction) + structure = function() { this.x = 0;

@@ -221,6 +226,7 @@ {

map.data[x][y].structure.hp = map.data[x][y].structure.hp - (attack.src.pow + attack.src.equipment.grade); if (map.data[x][y].structure.hp <= 0) { + AI.targetReset(map.data[x][y].structure); destroyStruct(x,y); } }

@@ -235,6 +241,7 @@ z = teams.p1;

} else { z = teams.cpu; } w = z.structs.indexOf(map.data[x][y].structure); + if (z.structs[w].name == "p1 HQ") { gameState.flow = "youLose";
M Team.jsTeam.js

@@ -1,3 +1,7 @@

+// Team.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains data structures and basic functions +// relating to the Teams (ie P1 and CPU) + function defTeams() { this.p1 = {};
M Touch.jsTouch.js

@@ -1,3 +1,6 @@

+// Touch.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains the touch controls! + function doNothing(e) {
M Unit.jsUnit.js

@@ -1,3 +1,8 @@

+// Unit.js & uStrat (c) Derek Stevens <drkste@zoho.com> +// this file contains everything related to units, +// including constructors, factories, and methods on units +// (ie damage and destruction) + function Unit() { this.sprite = {};

@@ -192,6 +197,7 @@ {

map.data[x][y].unit.hp = map.data[x][y].unit.hp - (attack.src.pow + attack.src.equipment.grade); if (map.data[x][y].unit.hp <= 0) { + AI.targetReset(map.data[x][y].unit); destroyUnit(x,y); } }