├── README.md
├── index.html
├── codemirror.css
├── reference.html
├── script.js
└── javascript.js
/README.md:
--------------------------------------------------------------------------------
1 | ## GitHub Game Off 2016
2 | # HackShot
3 |
4 | A small Javascript coding game where you control a lone gun tower against hordes of incoming enemies.
5 | Play it [here](http://buch415.github.io/game-off-2016), and check out the
6 | [manual and reference](http://buch415.github.io/game-off-2016/reference.html)
7 |
8 | Uses [CodeMirror](https://codemirror.net/) for code editors and syntax highlighting.
9 |
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
67 |
68 |
The aim of this game is to programmatically control a cannon to shoot
69 | and destroy hordes of enemies. The cannon exposes a JavaScript API for you
70 | to use to control it. You will find some examples and a
71 | complete API reference below.
72 |
73 |
Every time an enemy reaches the cannon, the cannon
74 | loses 1 health point (out of 10). Once health goes down to 0, you lose.
75 |
76 |
The cannon can basically perform two actions: it can
77 | shoot or self-repair from damage taken. Both actions will require some
78 | time for the cannon to be ready again.
79 |
80 |
81 |
You can aim the cannon using the function
82 | this.setCannonAngle (A), which takes as a parameter
83 | the angle you want to point the cannon at, measured in radians, counter-clockwise,
84 | starting from the horizontal (which means, 0 is horizontal, Math.PI / is
85 | vertical). The cannon has a minimum and maximum angle, which you can
86 | respectively read in the variables cannonMinAngle and cannonMaxAngle.
87 | When the function is called, the cannon starts moving towards the wanted
88 | angle. You can check what angle it currently is at using the function
89 | this.getCannonAngle().
90 |
91 |
92 |
93 |
Using the function this.shoot(), the
94 | cannon will shoot if ready. After shooting, it will have to reload and
95 | won't be able to shoot for a while.
96 |
97 |
98 |
99 |
You can recover your cannon's health with the function
100 | this.repair(). As for shooting, this will take some
101 | time for the cannon to be ready again to shoot or repair. Repair reload
102 | time is twice as long as shoot reload time.
103 |
104 |
105 |
106 |
You can access information about the enemies via the
107 | this.getEnemies() function. Enemies can come
108 | both on the ground or in the air. The pattern they will follow depends
109 | on the test you choose.
110 |
111 |
112 |
113 |
The score will be calculated as follows:
114 | [survived time] + 5*[kills] + 20*[accuracy] - [repairs]
115 |
116 |
117 |
118 |
setup: function () { /* Miscellaneous setup things here */ },
119 |
120 | update: function () {
121 | this.shoot(); // Shoots the cannon
122 | }
123 |
124 |
125 |
setup: function() {
126 | this.setCannonAngle ( cannonMaxAngle );
127 | },
128 |
129 | update: function () {
130 | // Moves the cannon from up to down continuously
131 | if ( this.getCannonAngle() == cannonMaxAngle )
132 | this.setCannonAngle ( cannonMinAngle );
133 | if ( this.getCannonAngle() == cannonMinAngle )
134 | this.setCannonAngle ( cannonMaxAngle );
135 |
136 | this.shoot();
137 | }
138 |
139 |
140 |
setup: function () { /* Miscellaneous setup things here */ },
141 |
142 | update: function () {
143 | if ( this.hp() < 5 ) // EMERGENCY! Low health => repair
144 | this.repair();
145 |
146 | this.shoot();
147 | }
148 |
149 |
150 |
151 |
152 | this.shoot():
153 | shoots the cannon; it works only if the cannon
154 | is ready; you can check that programmatically via the
155 | this.ready() function. The function returns 1
156 | if the cannon could shoot, 0 otherwise.
157 |
158 |
159 |
160 | this.repair():
161 | repairs your cannon, restoring 1 hit point; it
162 | works only if the cannon is ready; you can check that programmatically via the
163 | this.ready() function. The function returns 1
164 | if the cannon could repair, 0 otherwise.
165 |
166 |
167 |
168 | this.setCannonAngle(A):
169 | tells the cannon to move to angle A, given in radians
170 | (0 means horizontal, Math.PI/2 means vertical - note that cannon angle has
171 | a maximum and a minimum, see more here).
172 |
173 |
174 |
175 | this.getCannonAngle():
176 | returns current cannon angle, in radians; 0 means
177 | horizontal and Math.PI/2 means vertical (note that cannon angle has
178 | a maximum and a minimum, see more here).
179 |
180 |
181 |
182 | this.getEnemies():
183 | returns the current alive enemies as an array;
184 | each element of the array is a two-element array [x,y], where x is
185 | the x coordinate of the enemy (0 is the cannon's base center position)
186 | and y is the vertical coordinate (0 is the ground). Enemies are sorted
187 | in ascending x order, so that the first element of the array is always
188 | the closest to the cannon.
189 |
190 |
191 |
192 | this.ready():
193 | returns 1 if the cannon is ready to shoot,
194 | or repair 0 otherwise.
195 |
196 |
197 |
198 | this.hp():
199 | returns the amount of hit points left the cannon has.
200 |
201 |
202 |
203 | this.mark(i):
204 | you can use this function to mark the enemy of index i.
205 | The marked enemy is graphically highlighted. You can use this function for
206 | debugging purposes; you can mark only one enemy at a time, so the only
207 | actually marked enemy will be the last one on which this function was called.
208 |
209 |
210 |
211 | this.unmark():
212 | removes the mark from the currently marked enemy.
213 |
214 |
215 |
216 |
217 | cannonShootSpeed:
218 | the speed of the cannon projectile when shot.
219 |
220 |
221 |
222 | g:
223 | gravity force.
224 |
225 |
226 |
227 | controllerT:
228 | game time in seconds between two subsequent calls of the update() function
229 | (game time is measured with respect to the timer in the top left corner of game screen).
230 |
231 |
232 |
233 | cannonMinAngle:
234 | minimum cannon angle.
235 |
236 |
237 |
238 | cannonMaxAngle:
239 | maximum cannon angle.
240 |
241 |
242 |
243 |
244 |
--------------------------------------------------------------------------------
/script.js:
--------------------------------------------------------------------------------
1 | var canvas = 0, context = 0;
2 | const fps = 60;
3 |
4 | // Graphics settings of sorts
5 | var gfx_lvlColor_alive = "#7fff3f";
6 | var gfx_lvlColor_dead = "#ff2323";
7 | var gfx_bgColor = "#101010";
8 | var gfx_rangeColor = "#151515";
9 | var gfx_linew = 4;
10 |
11 | var gfx_enemyH = 17;
12 | var gfx_enemyR = 10;
13 |
14 | var levelY = 0; // Level base y
15 |
16 | const cannonR = 20; // Cannon base radius
17 | const cannonL = 20; // Cannon length
18 | const cannonX = 20; // Cannon X coordinate
19 | const cannonSpeed = (Math.PI / 2); // Cannon rotation speed
20 | const cannonShootSpeed = 350; // Bullet (initial) speed
21 |
22 | const cannonMinAngle = Math.PI / 16;
23 | const cannonMaxAngle = Math.PI / 2 - cannonMinAngle;
24 |
25 | const cannonMaxHP = 10;
26 |
27 | const g = 200; // Gravity
28 |
29 | const shootReloadSpeed = 1; // Cannon reload speed
30 | const repairReloadSpeed = 0.5; // Repair reload speed
31 | const boomRange = 20; // Bullet explosion range
32 |
33 | const controllerT = 1 / 10 / fps;
34 |
35 | var gameSpeed = 1;
36 | var pause = 0; // 1 = play, 0 = pause
37 | var _gameOver = 0;
38 |
39 | var spawners = [
40 | {
41 | id: "exp_amount",
42 | name: "Exp_Am",
43 | desc: "the enemy spawn frequency increases exponentially over time",
44 | spawnTimeMin: 0,
45 | spawnTimeMax: 3,
46 | spawnTimeT: 50,
47 | speed: 60,
48 | condition: function ( l ) {
49 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT );
50 | },
51 | spawn: function ( l ) {
52 | return [ canvas.width + gfx_enemyR, 0, 60 ];
53 | }
54 | },
55 |
56 | {
57 | id: "exp_amount_air",
58 | name: "Exp_Air",
59 | desc: "the enemy spawn frequency increases exponentially over time",
60 | spawnTimeMin: 0,
61 | spawnTimeMax: 3,
62 | spawnTimeT: 50,
63 | speed: 60,
64 | condition: function ( l ) {
65 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT );
66 | },
67 | spawn: function ( l ) {
68 | return [ canvas.width + gfx_enemyR, 40, 60 ];
69 | }
70 | },
71 |
72 | {
73 | id: "exp_all",
74 | name: "Exp_All",
75 | desc: "the enemy spawn frequency and speed increases exponentially over time",
76 | spawnTimeMin: 0,
77 | spawnTimeMax: 3,
78 | spawnTimeT: 50,
79 | speedMin: 60,
80 | speedMax: 100,
81 | speedT: 50,
82 | condition: function ( l ) {
83 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT );
84 | },
85 | spawn: function ( l ) {
86 | return [ canvas.width + gfx_enemyR, 0, this.speedMax - (this.speedMax - this.speedMin) * Math.exp ( -l.time / this.speedT ) ];
87 | }
88 | },
89 |
90 | {
91 | id: "packs",
92 | name: "Packs",
93 | desc: "enemies are spawned in packs",
94 | packSize: 0,
95 | spawnedPacks: 0,
96 | air: 0,
97 | packSizeMin: 1,
98 | packSizeMax: 10,
99 | packSizeT: 50,
100 | packT: 0.38,
101 | spawnTime: 2.5,
102 | condition: function ( l ) {
103 | if ( this.packSize > 0 && l.time - l.lastSpawn > this.packT ) { this.packSize--; return 1; }
104 | else if ( l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTime ) {
105 | this.packSize = this.packSizeMax - Math.floor ( (this.packSizeMax - this.packSizeMin) * Math.exp ( -l.time / this.packSizeT ) );
106 | this.spawnedPacks++;
107 | this.air = (this.spawnedPacks % 4) == 0;
108 | }
109 | },
110 | spawn: function ( l ) {
111 | return [ canvas.width + gfx_enemyR, this.air * 40, 60 ];
112 | }
113 | },
114 |
115 | {
116 | id: "air_var",
117 | name: "Air",
118 | desc: "enemies fly at different heights",
119 | spawnTimeMin: 0,
120 | spawnTimeMax: 3,
121 | spawnTimeT: 50,
122 | speed: 60,
123 | condition: function ( l ) {
124 | return l.lastSpawn < 0 || l.time - l.lastSpawn > this.spawnTimeMin + (this.spawnTimeMax - this.spawnTimeMin) * Math.exp ( -l.time / this.spawnTimeT );
125 | },
126 | spawn: function ( l ) {
127 | return [ canvas.width + gfx_enemyR, 30 + 30 * Math.sin((l.time % 5) / 5), 60 ];
128 | }
129 | },
130 |
131 | {
132 | id: "rand",
133 | name: "Rand",
134 | desc: "many random things",
135 | spawnChanceBase: 0.005,
136 | spawnChanceMax: 0.025,
137 | spawnChanceT: 50,
138 | speedBase: 50,
139 | speedDelta: 30,
140 | speedT: 50,
141 | condition: function ( l ) {
142 | return Math.random() < this.spawnChanceMax - ( this.spawnChanceMax - this.spawnChanceBase ) * Math.exp ( -l.time / this.spawnChanceT );
143 | },
144 | spawn: function ( l ) {
145 | var h = [ 0, 30, 60 ];
146 | return [ canvas.width + gfx_enemyR, h [ Math.floor(Math.random() * 3) ],
147 | this.speedBase + this.speedDelta * ( 1 - Math.exp ( -l.time / this.speedT ) ) * Math.random() ];
148 | }
149 | }
150 | ];
151 |
152 | function togglePause () {
153 | pause = 1 - pause;
154 | pauseBtn = document.getElementById("pause");
155 |
156 | if ( pause ) pauseBtn.innerText = "Pause";
157 | else pauseBtn.innerText = "Play";
158 | }
159 |
160 | function unpauseGame () {
161 | pause = 1;
162 | pauseBtn = document.getElementById("pause");
163 | pauseBtn.innerText = "Pause";
164 | }
165 |
166 | function pauseGame () {
167 | pause = 0;
168 | pauseBtn = document.getElementById("pause");
169 | pauseBtn.innerText = "Play";
170 | }
171 |
172 | function gradientCol ( a, b, k ) {
173 | var ar = parseInt ( a.substr(1,2), 16 );
174 | var ag = parseInt ( a.substr(3,2), 16 );
175 | var ab = parseInt ( a.substr(5,2), 16 );
176 |
177 | var br = parseInt ( b.substr(1,2), 16 );
178 | var bg = parseInt ( b.substr(3,2), 16 );
179 | var bb = parseInt ( b.substr(5,2), 16 );
180 |
181 | var mida = Math.round(ar * k + br * (1-k));
182 | var midg = Math.round(ag * k + bg * (1-k));
183 | var midb = Math.round(ab * k + bb * (1-k));
184 |
185 | function hex (x) {
186 | var s = x.toString(16);
187 | return s.length > 1 ? s : ('0' + s);
188 | }
189 |
190 | return "#" + hex(mida) + hex(midg) + hex(midb);
191 | }
192 |
193 | var setup = function () {
194 | canvas = document.getElementById("canvas");
195 | context = canvas.getContext("2d");
196 | context.lineJoin = "round";
197 |
198 | if ( localStorage.controller != undefined )
199 | document.getElementById("code").innerText = localStorage.controller;
200 |
201 | codeMirror = CodeMirror.fromTextArea(document.getElementById("code"), { lineNumbers: true } );
202 |
203 | levelY = 7 * canvas.height / 8;
204 |
205 | document.getElementById("apply").onclick = function () {
206 | mainLevel.reset();
207 | _gameOver = 0;
208 | mainLevel.applyController ( codeMirror.doc.getValue() );
209 | localStorage.controller = codeMirror.doc.getValue();
210 | unpauseGame();
211 | }
212 |
213 | document.getElementById("pause").onclick = togglePause;
214 |
215 | document.getElementById("slow").onclick = function () {
216 | gameSpeed -= 1; if ( gameSpeed < 1 ) gameSpeed = 1;
217 | document.getElementById("speed").innerText = "speed: " + gameSpeed;
218 | localStorage.gameSpeed = gameSpeed;
219 | }
220 | document.getElementById("fast").onclick = function () {
221 | gameSpeed += 1;
222 | document.getElementById("speed").innerText = "speed: " + gameSpeed;
223 | localStorage.gameSpeed = gameSpeed;
224 | }
225 |
226 | for ( var i = 0; i < spawners.length; i++ ) {
227 | $('