1 | #!/usr/bin/env node
|
2 |
|
3 | /**
|
4 | * ping
|
5 | * https://github.com/chjj/blessed
|
6 | * Copyright (c) 2013, Christopher Jeffrey (MIT License)
|
7 | * Online (ping)pong in your terminal.
|
8 | */
|
9 |
|
10 | // Example Usage:
|
11 | // Server: $ ./example/ping 3000
|
12 | // Client: $ ./example/ping 127.0.0.1 3000
|
13 | // Demo: $ ./example/ping
|
14 |
|
15 | process.title = 'ping';
|
16 |
|
17 | if (/^(-h|--help|-\?)$/.test(process.argv[2])) {
|
18 | console.log('node-ping');
|
19 | console.log('Example Usage:');
|
20 | console.log('Server: $ node-ping 3000');
|
21 | console.log('Client: $ node-ping 127.0.0.1 3000');
|
22 | console.log('Demo: $ node-ping');
|
23 | return process.exit(0);
|
24 | }
|
25 |
|
26 | var blessed = require('blessed')
|
27 | , nssocket;
|
28 |
|
29 | try {
|
30 | nssocket = require('nssocket');
|
31 | } catch (e) {
|
32 | ;
|
33 | }
|
34 |
|
35 | var server
|
36 | , socket;
|
37 |
|
38 | /**
|
39 | * Screen Layout
|
40 | */
|
41 |
|
42 | var screen = blessed.screen();
|
43 |
|
44 | var table = blessed.box({
|
45 | left: 0,
|
46 | top: 0,
|
47 | width: screen.width,
|
48 | height: screen.height
|
49 | });
|
50 |
|
51 | var ball = blessed.box({
|
52 | width: 1,
|
53 | height: 1,
|
54 | bg: 'white',
|
55 | top: 0,
|
56 | left: 0
|
57 | });
|
58 |
|
59 | var lpaddle = blessed.box({
|
60 | width: 1,
|
61 | height: 3,
|
62 | bg: 'yellow',
|
63 | top: 0,
|
64 | left: 0
|
65 | });
|
66 |
|
67 | var rpaddle = blessed.box({
|
68 | width: 1,
|
69 | height: 3,
|
70 | bg: 'yellow',
|
71 | top: 0,
|
72 | right: 0
|
73 | });
|
74 |
|
75 | var score = blessed.box({
|
76 | top: 0,
|
77 | left: 4,
|
78 | height: 3,
|
79 | width: 'shrink',
|
80 | border: {
|
81 | type: 'line'
|
82 | },
|
83 | //align: 'center',
|
84 | style: {
|
85 | bold: true
|
86 | },
|
87 | tags: true
|
88 | });
|
89 |
|
90 | score.lwins = 0;
|
91 | score.rwins = 0;
|
92 |
|
93 | var net = blessed.box({
|
94 | width: 1,
|
95 | height: '100%',
|
96 | bg: 'yellow',
|
97 | top: 0,
|
98 | left: 'center'
|
99 | });
|
100 |
|
101 | var message = blessed.box({
|
102 | width: '50%',
|
103 | height: 3,
|
104 | border: {
|
105 | type: 'line'
|
106 | },
|
107 | top: 'center',
|
108 | left: 'center'
|
109 | });
|
110 |
|
111 | var text = blessed.box({
|
112 | top: 'center',
|
113 | left: 1,
|
114 | right: 1,
|
115 | height: 1,
|
116 | align: 'center',
|
117 | content: 'Waiting for players to connect...'
|
118 | });
|
119 |
|
120 | message.append(text);
|
121 |
|
122 | screen.append(table);
|
123 |
|
124 | table.append(score);
|
125 | table.append(lpaddle);
|
126 | table.append(rpaddle);
|
127 | table.append(net);
|
128 | table.append(ball);
|
129 | table.append(message);
|
130 |
|
131 | screen.on('resize', function() {
|
132 | table.width = screen.width;
|
133 | table.height = screen.height;
|
134 | [ball, lpaddle, rpaddle].forEach(function(el) {
|
135 | if (el.rbottom < 0) el.rtop = table.height - 1 - el.height;
|
136 | if (el.rright < 0) el.rleft = table.width - 1;
|
137 | });
|
138 | screen.render();
|
139 | sync();
|
140 | });
|
141 |
|
142 | /**
|
143 | * Options
|
144 | */
|
145 |
|
146 | ball.speed = 2;
|
147 | ball.unpredictable = true;
|
148 | lpaddle.speed = 2;
|
149 | rpaddle.speed = 2;
|
150 |
|
151 | /**
|
152 | * Game
|
153 | */
|
154 |
|
155 | function sync() {
|
156 | if (!socket) return;
|
157 | socket.send(['update'], {
|
158 | table: { width: table.width, height: table.height },
|
159 | lpaddle: { rleft: lpaddle.rleft, rtop: lpaddle.rtop, speed: lpaddle.speed },
|
160 | rpaddle: { rleft: rpaddle.rleft, rtop: rpaddle.rtop, speed: rpaddle.speed },
|
161 | score: { lwins: score.lwins, rwins: score.rwins }
|
162 | });
|
163 | }
|
164 |
|
165 | function reset() {
|
166 | text.setContent('Waiting for players to connect...');
|
167 | message.hide();
|
168 | ball.moving = true;
|
169 | ball.direction = 'right';
|
170 | ball.angle = 'down';
|
171 | ball.rtop = 1;
|
172 | ball.rleft = 1;
|
173 | if ((score.lwins + score.rwins) % 2 !== 0) {
|
174 | ball.direction = 'left';
|
175 | ball.rleft = table.width - 1;
|
176 | }
|
177 | lpaddle.rtop = 0;
|
178 | rpaddle.rtop = 0;
|
179 |
|
180 | score.setContent('{green-fg}Score:{/} ' + score.lwins + ' | ' + score.rwins);
|
181 |
|
182 | rpaddle.movable = true;
|
183 |
|
184 | screen.render();
|
185 | if (server && socket) {
|
186 | socket.send(['reset']);
|
187 | }
|
188 | }
|
189 |
|
190 | function rand(min, max) {
|
191 | return Math.floor(Math.random() * (max - min + 1)) + min;
|
192 | }
|
193 |
|
194 | function startGame() {
|
195 | reset();
|
196 |
|
197 | if (startGame._bound) return;
|
198 | startGame._bound = true;
|
199 |
|
200 | screen.on('keypress', function(ch, key) {
|
201 | if (!ball.moving) return;
|
202 | if (key.name === 'up' || key.name === 'k') {
|
203 | if (socket) socket.send(['up']);
|
204 | if (lpaddle.rtop > 0) lpaddle.rtop -= lpaddle.speed;
|
205 | if (!socket) if (rpaddle.rtop > 0) rpaddle.rtop -= rpaddle.speed;
|
206 | if (lpaddle.rtop < 0) lpaddle.rtop = 0;
|
207 | if (rpaddle.rtop < 0) rpaddle.rtop = 0;
|
208 | screen.render();
|
209 | } else if (key.name === 'down' || key.name === 'j') {
|
210 | if (socket) socket.send(['down']);
|
211 | if (lpaddle.rbottom > 0) lpaddle.rtop += lpaddle.speed;
|
212 | if (!socket) if (rpaddle.rbottom > 0) rpaddle.rtop += rpaddle.speed;
|
213 | if (lpaddle.rbottom < 0) lpaddle.rtop = table.height - lpaddle.height - 1;
|
214 | if (rpaddle.rbottom < 0) rpaddle.rtop = table.height - rpaddle.height - 1;
|
215 | screen.render();
|
216 | }
|
217 | });
|
218 |
|
219 | setInterval(function() {
|
220 | if (!ball.moving) return;
|
221 | if (ball.direction === 'right') {
|
222 | if (ball.rright > 1) {
|
223 | ball.rleft += ball.speed;
|
224 | } else {
|
225 | if (ball.rtop >= rpaddle.rtop && ball.rtop <= rpaddle.rtop + rpaddle.height) {
|
226 | ball.direction = 'left';
|
227 | ball.rleft -= ball.speed;
|
228 |
|
229 | ball.rleft -= rand(0, 3);
|
230 | if (ball.angle === 'down') ball.rtop += rand(0, 3);
|
231 | else if (ball.angle === 'up') ball.rtop -= rand(0, 3);
|
232 | } else {
|
233 | // Right loses
|
234 | score.lwins++;
|
235 | ball.rleft = table.width - 1;
|
236 | if (socket) socket.send(['lose']);
|
237 | ball.moving = false;
|
238 | text.setContent('Right player loses!');
|
239 | message.show();
|
240 | setTimeout(reset, 3000);
|
241 | screen.render();
|
242 | return;
|
243 | }
|
244 | }
|
245 | if (ball.rright < 1) ball.rleft = table.width - 2;
|
246 | } else if (ball.direction === 'left') {
|
247 | if (ball.rleft > 1) {
|
248 | ball.rleft -= ball.speed;
|
249 | } else {
|
250 | if (ball.rtop >= lpaddle.rtop && ball.rtop <= lpaddle.rtop + lpaddle.height) {
|
251 | ball.direction = 'right';
|
252 | ball.rleft += ball.speed;
|
253 |
|
254 | ball.rleft += rand(0, 3);
|
255 | if (ball.angle === 'down') ball.rtop += rand(0, 3);
|
256 | else if (ball.angle === 'up') ball.rtop -= rand(0, 3);
|
257 | } else {
|
258 | // Left loses
|
259 | score.rwins++;
|
260 | ball.rleft = 0;
|
261 | if (socket) socket.send(['win']);
|
262 | ball.moving = false;
|
263 | text.setContent('Left player loses!');
|
264 | message.show();
|
265 | setTimeout(reset, 3000);
|
266 | screen.render();
|
267 | return;
|
268 | }
|
269 | }
|
270 | if (ball.rleft < 1) ball.rleft = 1;
|
271 | }
|
272 | if (ball.angle === 'down') {
|
273 | if (ball.rbottom > 0) {
|
274 | ball.rtop++;
|
275 | if (ball.unpredictable) ball.rtop += rand(0, 3);
|
276 | } else {
|
277 | ball.angle = 'up';
|
278 | ball.rtop--;
|
279 | }
|
280 | } else if (ball.angle === 'up') {
|
281 | if (ball.rtop > 0) {
|
282 | ball.rtop--;
|
283 | if (ball.unpredictable) ball.rtop -= rand(0, 3);
|
284 | } else {
|
285 | ball.angle = 'down';
|
286 | ball.rtop++;
|
287 | }
|
288 | }
|
289 | if (ball.rtop < 0) ball.rtop = 0;
|
290 | if (ball.rbottom < 0) ball.rtop = table.height - 1;
|
291 | if (socket) socket.send(['ball'], { rleft: ball.rleft, rtop: ball.rtop });
|
292 | screen.render();
|
293 | }, 100);
|
294 | }
|
295 |
|
296 | function startServer() {
|
297 | server = nssocket.createServer({}, function(socket_) {
|
298 | socket = socket_;
|
299 |
|
300 | sync();
|
301 |
|
302 | socket.data(['up'], function() {
|
303 | if (!ball.moving) return;
|
304 | if (rpaddle.rtop > 0) rpaddle.rtop -= rpaddle.speed;
|
305 | screen.render();
|
306 | });
|
307 |
|
308 | socket.data(['down'], function() {
|
309 | if (!ball.moving) return;
|
310 | if (rpaddle.rtop < table.height - 1) rpaddle.rtop += rpaddle.speed;
|
311 | screen.render();
|
312 | });
|
313 |
|
314 | socket.on('error', function() {
|
315 | socket = null;
|
316 | reset();
|
317 | ball.moving = false;
|
318 | message.show();
|
319 | screen.render();
|
320 | });
|
321 |
|
322 | startGame();
|
323 | });
|
324 |
|
325 | server.listen(+process.argv[2]);
|
326 | }
|
327 |
|
328 | function startClient() {
|
329 | var socket = new nssocket.NsSocket({
|
330 | reconnect: true,
|
331 | maxRetries: Infinity,
|
332 | retryInterval: 5000
|
333 | });
|
334 |
|
335 | socket.connect(+process.argv[3], process.argv[2]);
|
336 |
|
337 | screen.on('keypress', function(ch, key) {
|
338 | if (!rpaddle.movable) return;
|
339 | if (key.name === 'up' || key.name === 'k') {
|
340 | socket.send(['up']);
|
341 | if (rpaddle.rtop > 0) rpaddle.rtop -= rpaddle.speed;
|
342 | if (rpaddle.rtop < 0) rpaddle.rtop = 0;
|
343 | screen.render();
|
344 | } else if (key.name === 'down' || key.name === 'j') {
|
345 | socket.send(['down']);
|
346 | if (rpaddle.rbottom > 0) rpaddle.rtop += rpaddle.speed;
|
347 | if (rpaddle.rbottom < 0) rpaddle.rtop = table.height - rpaddle.height - 1;
|
348 | screen.render();
|
349 | }
|
350 | });
|
351 |
|
352 | socket.data(['up'], function() {
|
353 | if (lpaddle.rtop > 0) lpaddle.rtop -= lpaddle.speed;
|
354 | screen.render();
|
355 | });
|
356 |
|
357 | socket.data(['down'], function() {
|
358 | if (lpaddle.rtop < table.height - 1) lpaddle.rtop += lpaddle.speed;
|
359 | screen.render();
|
360 | });
|
361 |
|
362 | socket.data(['ball'], function(data) {
|
363 | ball.rleft = data.rleft;
|
364 | ball.rtop = data.rtop;
|
365 | screen.render();
|
366 | });
|
367 |
|
368 | socket.data(['update'], function(data) {
|
369 | if (data.lpaddle) {
|
370 | lpaddle.rleft = data.lpaddle.rleft;
|
371 | lpaddle.rtop = data.lpaddle.rtop;
|
372 | lpaddle.speed = data.lpaddle.speed;
|
373 | }
|
374 |
|
375 | if (data.rpaddle) {
|
376 | rpaddle.rleft = data.rpaddle.rleft;
|
377 | rpaddle.rtop = data.rpaddle.rtop;
|
378 | rpaddle.speed = data.rpaddle.speed;
|
379 | }
|
380 |
|
381 | if (data.ball) {
|
382 | ball.moving = data.ball.moving;
|
383 | ball.rleft = data.ball.rleft;
|
384 | ball.rtop = data.ball.rtop;
|
385 | }
|
386 |
|
387 | if (data.table) {
|
388 | table.height = data.table.height;
|
389 | table.width = data.table.width;
|
390 | }
|
391 |
|
392 | if (data.score) {
|
393 | score.lwins = data.score.lwins;
|
394 | score.rwins = data.score.rwins;
|
395 | }
|
396 |
|
397 | screen.render();
|
398 | });
|
399 |
|
400 | socket.data(['win'], function() {
|
401 | rpaddle.movable = false;
|
402 | score.rwins++;
|
403 | text.setContent('Left player loses!');
|
404 | message.show();
|
405 | screen.render();
|
406 | });
|
407 |
|
408 | socket.data(['lose'], function() {
|
409 | rpaddle.movable = false;
|
410 | score.lwins++;
|
411 | text.setContent('Right player loses!');
|
412 | message.show();
|
413 | screen.render();
|
414 | });
|
415 |
|
416 | socket.data(['reset'], reset);
|
417 |
|
418 | reset();
|
419 | }
|
420 |
|
421 | /**
|
422 | * Main
|
423 | */
|
424 |
|
425 | function main() {
|
426 | screen.on('keypress', function(ch, key) {
|
427 | if (key.name === 'q' || key.name === 'escape') {
|
428 | return process.exit(0);
|
429 | }
|
430 | });
|
431 |
|
432 | screen.render();
|
433 |
|
434 | // Demo Mode / Single Player
|
435 | if (!nssocket || !process.argv[2]) return startGame();
|
436 |
|
437 | // Server Mode
|
438 | if (!process.argv[3]) return startServer();
|
439 |
|
440 | // Client Mode
|
441 | if (process.argv[2] && process.argv[3]) return startClient();
|
442 | }
|
443 |
|
444 | /**
|
445 | * Execute
|
446 | */
|
447 |
|
448 | main();
|