Added Logitech Keyboard Hacking
This commit is contained in:
parent
e644618355
commit
d071644e3d
265
OneLoneCoder_CommandLineFPS.cpp
Normal file
265
OneLoneCoder_CommandLineFPS.cpp
Normal file
@ -0,0 +1,265 @@
|
|||||||
|
/*
|
||||||
|
OneLoneCoder.com - Command Line First Person Shooter (FPS) Engine
|
||||||
|
"Why were games not done like this is 1990?" - @Javidx9
|
||||||
|
|
||||||
|
Disclaimer
|
||||||
|
~~~~~~~~~~
|
||||||
|
I don't care what you use this for. It's intended to be educational, and perhaps
|
||||||
|
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
||||||
|
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
||||||
|
bad that happens as a result of your actions. However, if good stuff happens, I
|
||||||
|
would appreciate a shout out, or at least give the blog some publicity for me.
|
||||||
|
Cheers!
|
||||||
|
|
||||||
|
Background
|
||||||
|
~~~~~~~~~~
|
||||||
|
Whilst waiting for TheMexicanRunner to start the finale of his NesMania project,
|
||||||
|
his Twitch stream had a counter counting down for a couple of hours until it started.
|
||||||
|
With some time on my hands, I thought it might be fun to see what the graphical
|
||||||
|
capabilities of the console are. Turns out, not very much, but hey, it's nice to think
|
||||||
|
Wolfenstein could have existed a few years earlier, and in just ~200 lines of code.
|
||||||
|
|
||||||
|
IMPORTANT!!!!
|
||||||
|
~~~~~~~~~~~~~
|
||||||
|
READ ME BEFORE RUNNING!!! This program expects the console dimensions to be set to
|
||||||
|
120 Columns by 40 Rows. I recommend a small font "Consolas" at size 16. You can do this
|
||||||
|
by running the program, and right clicking on the console title bar, and specifying
|
||||||
|
the properties. You can also choose to default to them in the future.
|
||||||
|
|
||||||
|
Future Modifications
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
1) Shade block segments based on angle from player, i.e. less light reflected off
|
||||||
|
walls at side of player. Walls straight on are brightest.
|
||||||
|
2) Find an interesting and optimised ray-tracing method. I'm sure one must exist
|
||||||
|
to more optimally search the map space
|
||||||
|
3) Add bullets!
|
||||||
|
4) Add bad guys!
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
Twitter: @javidx9
|
||||||
|
Blog: www.onelonecoder.com
|
||||||
|
|
||||||
|
Video:
|
||||||
|
~~~~~~
|
||||||
|
xxxxxxx
|
||||||
|
|
||||||
|
Last Updated: 27/02/2017
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
#include <utility>
|
||||||
|
#include <algorithm>
|
||||||
|
#include <chrono>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <Windows.h>
|
||||||
|
|
||||||
|
int nScreenWidth = 120; // Console Screen Size X (columns)
|
||||||
|
int nScreenHeight = 40; // Console Screen Size Y (rows)
|
||||||
|
int nMapWidth = 16; // World Dimensions
|
||||||
|
int nMapHeight = 16;
|
||||||
|
|
||||||
|
float fPlayerX = 14.7f; // Player Start Position
|
||||||
|
float fPlayerY = 5.09f;
|
||||||
|
float fPlayerA = 0.0f; // Player Start Rotation
|
||||||
|
float fFOV = 3.14159f / 4.0f; // Field of View
|
||||||
|
float fDepth = 16.0f; // Maximum rendering distance
|
||||||
|
float fSpeed = 5.0f; // Walking Speed
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// Create Screen Buffer
|
||||||
|
wchar_t *screen = new wchar_t[nScreenWidth*nScreenHeight];
|
||||||
|
HANDLE hConsole = CreateConsoleScreenBuffer(GENERIC_READ | GENERIC_WRITE, 0, NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
|
||||||
|
SetConsoleActiveScreenBuffer(hConsole);
|
||||||
|
DWORD dwBytesWritten = 0;
|
||||||
|
|
||||||
|
// Create Map of world space # = wall block, . = space
|
||||||
|
wstring map;
|
||||||
|
map += L"#########.......";
|
||||||
|
map += L"#...............";
|
||||||
|
map += L"#.......########";
|
||||||
|
map += L"#..............#";
|
||||||
|
map += L"#......##......#";
|
||||||
|
map += L"#......##......#";
|
||||||
|
map += L"#..............#";
|
||||||
|
map += L"###............#";
|
||||||
|
map += L"##.............#";
|
||||||
|
map += L"#......####..###";
|
||||||
|
map += L"#......#.......#";
|
||||||
|
map += L"#......#.......#";
|
||||||
|
map += L"#..............#";
|
||||||
|
map += L"#......#########";
|
||||||
|
map += L"#..............#";
|
||||||
|
map += L"################";
|
||||||
|
|
||||||
|
auto tp1 = chrono::system_clock::now();
|
||||||
|
auto tp2 = chrono::system_clock::now();
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// We'll need time differential per frame to calculate modification
|
||||||
|
// to movement speeds, to ensure consistant movement, as ray-tracing
|
||||||
|
// is non-deterministic
|
||||||
|
tp2 = chrono::system_clock::now();
|
||||||
|
chrono::duration<float> elapsedTime = tp2 - tp1;
|
||||||
|
tp1 = tp2;
|
||||||
|
float fElapsedTime = elapsedTime.count();
|
||||||
|
|
||||||
|
|
||||||
|
// Handle CCW Rotation
|
||||||
|
if (GetAsyncKeyState((unsigned short)'A') & 0x8000)
|
||||||
|
fPlayerA -= (fSpeed * 0.75f) * fElapsedTime;
|
||||||
|
|
||||||
|
// Handle CW Rotation
|
||||||
|
if (GetAsyncKeyState((unsigned short)'D') & 0x8000)
|
||||||
|
fPlayerA += (fSpeed * 0.75f) * fElapsedTime;
|
||||||
|
|
||||||
|
// Handle Forwards movement & collision
|
||||||
|
if (GetAsyncKeyState((unsigned short)'W') & 0x8000)
|
||||||
|
{
|
||||||
|
fPlayerX += sinf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
fPlayerY += cosf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#')
|
||||||
|
{
|
||||||
|
fPlayerX -= sinf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
fPlayerY -= cosf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle backwards movement & collision
|
||||||
|
if (GetAsyncKeyState((unsigned short)'S') & 0x8000)
|
||||||
|
{
|
||||||
|
fPlayerX -= sinf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
fPlayerY -= cosf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
if (map.c_str()[(int)fPlayerX * nMapWidth + (int)fPlayerY] == '#')
|
||||||
|
{
|
||||||
|
fPlayerX += sinf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
fPlayerY += cosf(fPlayerA) * fSpeed * fElapsedTime;;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int x = 0; x < nScreenWidth; x++)
|
||||||
|
{
|
||||||
|
// For each column, calculate the projected ray angle into world space
|
||||||
|
float fRayAngle = (fPlayerA - fFOV/2.0f) + ((float)x / (float)nScreenWidth) * fFOV;
|
||||||
|
|
||||||
|
// Find distance to wall
|
||||||
|
float fStepSize = 0.1f; // Increment size for ray casting, decrease to increase
|
||||||
|
float fDistanceToWall = 0.0f; // resolution
|
||||||
|
|
||||||
|
bool bHitWall = false; // Set when ray hits wall block
|
||||||
|
bool bBoundary = false; // Set when ray hits boundary between two wall blocks
|
||||||
|
|
||||||
|
float fEyeX = sinf(fRayAngle); // Unit vector for ray in player space
|
||||||
|
float fEyeY = cosf(fRayAngle);
|
||||||
|
|
||||||
|
// Incrementally cast ray from player, along ray angle, testing for
|
||||||
|
// intersection with a block
|
||||||
|
while (!bHitWall && fDistanceToWall < fDepth)
|
||||||
|
{
|
||||||
|
fDistanceToWall += fStepSize;
|
||||||
|
int nTestX = (int)(fPlayerX + fEyeX * fDistanceToWall);
|
||||||
|
int nTestY = (int)(fPlayerY + fEyeY * fDistanceToWall);
|
||||||
|
|
||||||
|
// Test if ray is out of bounds
|
||||||
|
if (nTestX < 0 || nTestX >= nMapWidth || nTestY < 0 || nTestY >= nMapHeight)
|
||||||
|
{
|
||||||
|
bHitWall = true; // Just set distance to maximum depth
|
||||||
|
fDistanceToWall = fDepth;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Ray is inbounds so test to see if the ray cell is a wall block
|
||||||
|
if (map.c_str()[nTestX * nMapWidth + nTestY] == '#')
|
||||||
|
{
|
||||||
|
// Ray has hit wall
|
||||||
|
bHitWall = true;
|
||||||
|
|
||||||
|
// To highlight tile boundaries, cast a ray from each corner
|
||||||
|
// of the tile, to the player. The more coincident this ray
|
||||||
|
// is to the rendering ray, the closer we are to a tile
|
||||||
|
// boundary, which we'll shade to add detail to the walls
|
||||||
|
vector<pair<float, float>> p;
|
||||||
|
|
||||||
|
// Test each corner of hit tile, storing the distance from
|
||||||
|
// the player, and the calculated dot product of the two rays
|
||||||
|
for (int tx = 0; tx < 2; tx++)
|
||||||
|
for (int ty = 0; ty < 2; ty++)
|
||||||
|
{
|
||||||
|
// Angle of corner to eye
|
||||||
|
float vy = (float)nTestY + ty - fPlayerY;
|
||||||
|
float vx = (float)nTestX + tx - fPlayerX;
|
||||||
|
float d = sqrt(vx*vx + vy*vy);
|
||||||
|
float dot = (fEyeX * vx / d) + (fEyeY * vy / d);
|
||||||
|
p.push_back(make_pair(d, dot));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort Pairs from closest to farthest
|
||||||
|
sort(p.begin(), p.end(), [](const pair<float, float> &left, const pair<float, float> &right) {return left.first < right.first; });
|
||||||
|
|
||||||
|
// First two/three are closest (we will never see all four)
|
||||||
|
float fBound = 0.01;
|
||||||
|
if (acos(p.at(0).second) < fBound) bBoundary = true;
|
||||||
|
if (acos(p.at(1).second) < fBound) bBoundary = true;
|
||||||
|
if (acos(p.at(2).second) < fBound) bBoundary = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Calculate distance to ceiling and floor
|
||||||
|
int nCeiling = (float)(nScreenHeight/2.0) - nScreenHeight / ((float)fDistanceToWall);
|
||||||
|
int nFloor = nScreenHeight - nCeiling;
|
||||||
|
|
||||||
|
// Shader walls based on distance
|
||||||
|
short nShade = ' ';
|
||||||
|
if (fDistanceToWall <= fDepth / 4.0f) nShade = 0x2588; // Very close
|
||||||
|
else if (fDistanceToWall < fDepth / 3.0f) nShade = 0x2593;
|
||||||
|
else if (fDistanceToWall < fDepth / 2.0f) nShade = 0x2592;
|
||||||
|
else if (fDistanceToWall < fDepth) nShade = 0x2591;
|
||||||
|
else nShade = ' '; // Too far away
|
||||||
|
|
||||||
|
if (bBoundary) nShade = ' '; // Black it out
|
||||||
|
|
||||||
|
for (int y = 0; y < nScreenHeight; y++)
|
||||||
|
{
|
||||||
|
// Each Row
|
||||||
|
if(y <= nCeiling)
|
||||||
|
screen[y*nScreenWidth + x] = ' ';
|
||||||
|
else if(y > nCeiling && y <= nFloor)
|
||||||
|
screen[y*nScreenWidth + x] = nShade;
|
||||||
|
else // Floor
|
||||||
|
{
|
||||||
|
// Shade floor based on distance
|
||||||
|
float b = 1.0f - (((float)y -nScreenHeight/2.0f) / ((float)nScreenHeight / 2.0f));
|
||||||
|
if (b < 0.25) nShade = '#';
|
||||||
|
else if (b < 0.5) nShade = 'x';
|
||||||
|
else if (b < 0.75) nShade = '.';
|
||||||
|
else if (b < 0.9) nShade = '-';
|
||||||
|
else nShade = ' ';
|
||||||
|
screen[y*nScreenWidth + x] = nShade;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display Stats
|
||||||
|
swprintf_s(screen, 40, L"X=%3.2f, Y=%3.2f, A=%3.2f FPS=%3.2f ", fPlayerX, fPlayerY, fPlayerA, 1.0f/fElapsedTime);
|
||||||
|
|
||||||
|
// Display Map
|
||||||
|
for (int nx = 0; nx < nMapWidth; nx++)
|
||||||
|
for (int ny = 0; ny < nMapWidth; ny++)
|
||||||
|
{
|
||||||
|
screen[(ny+1)*nScreenWidth + nx] = map[ny * nMapWidth + nx];
|
||||||
|
}
|
||||||
|
screen[((int)fPlayerX+1) * nScreenWidth + (int)fPlayerY] = 'P';
|
||||||
|
|
||||||
|
// Display Frame
|
||||||
|
screen[nScreenWidth * nScreenHeight - 1] = '\0';
|
||||||
|
WriteConsoleOutputCharacter(hConsole, screen, nScreenWidth * nScreenHeight, { 0,0 }, &dwBytesWritten);
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
137
OneLoneCoder_LogitechG13Twitch.cpp
Normal file
137
OneLoneCoder_LogitechG13Twitch.cpp
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
/*
|
||||||
|
OneLoneCoder.com - Program a Logitech Keyboard To Display Twitch Chat
|
||||||
|
"Put Your Money Where Your Mouth Is" - @Javidx9
|
||||||
|
|
||||||
|
Disclaimer
|
||||||
|
~~~~~~~~~~
|
||||||
|
I don't care what you use this for. It's intended to be educational, and perhaps
|
||||||
|
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
||||||
|
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
||||||
|
bad that happens as a result of your actions. However, if good stuff happens, I
|
||||||
|
would appreciate a shout out, or at least give the blog some publicity for me.
|
||||||
|
Cheers!
|
||||||
|
|
||||||
|
Background
|
||||||
|
~~~~~~~~~~
|
||||||
|
A very minimal example of using the Logitech SDK to interface with a keyboard display,
|
||||||
|
then uses a really sloppy implementation of sockets, to connect to a twitch chat
|
||||||
|
session, displaying the chat on the keybaord screen.
|
||||||
|
|
||||||
|
Future Modifications
|
||||||
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
|
1) Wrap around text display on screen
|
||||||
|
|
||||||
|
Author
|
||||||
|
~~~~~~
|
||||||
|
Twitter: @javidx9
|
||||||
|
Blog: www.onelonecoder.com
|
||||||
|
|
||||||
|
Video:
|
||||||
|
~~~~~~
|
||||||
|
https://youtu.be/8UXCo-GhiF0
|
||||||
|
|
||||||
|
Last Updated: 23/05/2017
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
#include <list>
|
||||||
|
using namespace std;
|
||||||
|
|
||||||
|
// Include Winsock
|
||||||
|
#include <winsock2.h>
|
||||||
|
#include <ws2tcpip.h>
|
||||||
|
|
||||||
|
// Include Logitech library
|
||||||
|
#include "LogitechLCDLib.h"
|
||||||
|
|
||||||
|
int main()
|
||||||
|
{
|
||||||
|
// Load the driver
|
||||||
|
LogiLcdInit(L"OneLoneCoder Test", LOGI_LCD_TYPE_MONO);
|
||||||
|
|
||||||
|
// Check hardware is connected
|
||||||
|
if (!LogiLcdIsConnected(LOGI_LCD_TYPE_MONO))
|
||||||
|
wcout << "Hardware not found" << endl;
|
||||||
|
|
||||||
|
// Load WinSock
|
||||||
|
WSADATA wsaData;
|
||||||
|
if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
|
||||||
|
wcout << "Could not start WinSock" << endl;
|
||||||
|
|
||||||
|
// Get address to twitch server
|
||||||
|
struct addrinfo *addr = nullptr;
|
||||||
|
if (getaddrinfo("irc.chat.twitch.tv", "6667", nullptr, &addr) != 0)
|
||||||
|
wcout << "Failed to get address info" << endl;
|
||||||
|
|
||||||
|
// Create socket
|
||||||
|
SOCKET sock = INVALID_SOCKET;
|
||||||
|
sock = socket(addr->ai_family, addr->ai_socktype, addr->ai_protocol);
|
||||||
|
|
||||||
|
// Connect to server via socket
|
||||||
|
int i = connect(sock, addr->ai_addr, (int)addr->ai_addrlen);
|
||||||
|
if (i != SOCKET_ERROR)
|
||||||
|
{
|
||||||
|
// Handshake with twitch
|
||||||
|
string s;
|
||||||
|
|
||||||
|
// Authenticate
|
||||||
|
s = "PASS oauth:###INSERT YOUR OAUTH HERE###\r\n";
|
||||||
|
send(sock, s.c_str(), s.length(), 0);
|
||||||
|
|
||||||
|
// Register your twitch name
|
||||||
|
s = "NICK javidx9\r\n";
|
||||||
|
send(sock, s.c_str(), s.length(), 0);
|
||||||
|
|
||||||
|
// Join a twitch chat
|
||||||
|
s = "JOIN #javidx9\r\n";
|
||||||
|
send(sock, s.c_str(), s.length(), 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
char buffer[100];
|
||||||
|
string s;
|
||||||
|
list<string> sLines = { "","","","" };
|
||||||
|
|
||||||
|
while (1)
|
||||||
|
{
|
||||||
|
// Get Twitch Server Message
|
||||||
|
int i = recv(sock, buffer, 100, 0);
|
||||||
|
for (int j = 0; j < i; j++)
|
||||||
|
{
|
||||||
|
s.append(1, buffer[j]);
|
||||||
|
if (buffer[j] == '\n')
|
||||||
|
{
|
||||||
|
// User name is between first ':' and '!'.
|
||||||
|
// User chat is after second ':'
|
||||||
|
size_t m = s.find('!');
|
||||||
|
string sUserName = s.substr(1, m - 1);
|
||||||
|
size_t n = s.find(':', m);
|
||||||
|
if (n != string::npos)
|
||||||
|
{
|
||||||
|
string chat = s.substr(n + 1);
|
||||||
|
cout << sUserName.c_str() << ": " << chat.c_str() << endl;
|
||||||
|
|
||||||
|
sLines.pop_front();
|
||||||
|
sLines.push_back(sUserName + ": " + chat);
|
||||||
|
}
|
||||||
|
s.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display 4 lines of text from list
|
||||||
|
int p = 0;
|
||||||
|
for (auto k : sLines)
|
||||||
|
{
|
||||||
|
wstring ws;
|
||||||
|
ws.assign(k.begin(), k.end());
|
||||||
|
LogiLcdMonoSetText(p, (wchar_t*)ws.c_str()); // yuck...
|
||||||
|
p++;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update display
|
||||||
|
LogiLcdUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
LogiLcdShutdown();
|
||||||
|
return 0;
|
||||||
|
}
|
@ -1,39 +1,39 @@
|
|||||||
/*
|
/*
|
||||||
OneLoneCoder.com - Command Line Tetris
|
OneLoneCoder.com - Command Line Tetris
|
||||||
"Put Your Money Where Your Mouth Is" - @Javidx9
|
"Put Your Money Where Your Mouth Is" - @Javidx9
|
||||||
|
|
||||||
Disclaimer
|
Disclaimer
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
I don't care what you use this for. It's intended to be educational, and perhaps
|
I don't care what you use this for. It's intended to be educational, and perhaps
|
||||||
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
to the oddly minded - a little bit of fun. Please hack this, change it and use it
|
||||||
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
in any way you see fit. BUT, you acknowledge that I am not responsible for anything
|
||||||
bad that happens as a result of your actions. However, if good stuff happens, I
|
bad that happens as a result of your actions. However, if good stuff happens, I
|
||||||
would appreciate a shout out, or at least give the blog some publicity for me.
|
would appreciate a shout out, or at least give the blog some publicity for me.
|
||||||
Cheers!
|
Cheers!
|
||||||
|
|
||||||
Background
|
Background
|
||||||
~~~~~~~~~~
|
~~~~~~~~~~
|
||||||
I made a video "8-Bits of advice for new programmers" (https://youtu.be/vVRCJ52g5m4)
|
I made a video "8-Bits of advice for new programmers" (https://youtu.be/vVRCJ52g5m4)
|
||||||
and suggested that building a tetris clone instead of Dark Sould IV might be a better
|
and suggested that building a tetris clone instead of Dark Sould IV might be a better
|
||||||
approach to learning to code. Tetris is nice as it makes you think about algorithms.
|
approach to learning to code. Tetris is nice as it makes you think about algorithms.
|
||||||
|
|
||||||
Controls are Arrow keys Left, Right & Down. Use Z to rotate the piece.
|
Controls are Arrow keys Left, Right & Down. Use Z to rotate the piece.
|
||||||
You score 25pts per tetronimo, and 2^(number of lines)*100 when you get lines.
|
You score 25pts per tetronimo, and 2^(number of lines)*100 when you get lines.
|
||||||
|
|
||||||
Future Modifications
|
Future Modifications
|
||||||
~~~~~~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~~~~~~
|
||||||
1) Show next block and line counter
|
1) Show next block and line counter
|
||||||
|
|
||||||
Author
|
Author
|
||||||
~~~~~~
|
~~~~~~
|
||||||
Twitter: @javidx9
|
Twitter: @javidx9
|
||||||
Blog: www.onelonecoder.com
|
Blog: www.onelonecoder.com
|
||||||
|
|
||||||
Video:
|
Video:
|
||||||
~~~~~~
|
~~~~~~
|
||||||
xxxxxxx
|
https://youtu.be/8OK8_tHeCIA
|
||||||
|
|
||||||
Last Updated: 30/03/2017
|
Last Updated: 30/03/2017
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user