SHOW:
|
|
- or go back to the newest paste.
1 | /////////////////////////////////////////////////////////////////////////////// | |
2 | // Grid Game | |
3 | /////////////////////////////////////////////////////////////////////////////// | |
4 | // Grid game board program that lets users move a cursor around on a board | |
5 | /////////////////////////////////////////////////////////////////////////////// | |
6 | ||
7 | #include <stdio.h> // For I/O | |
8 | #include <stdlib.h> // For rand | |
9 | #include <time.h> // For seeding Rand | |
10 | - | #include <string.h> // For memset |
10 | + | #include <string> // For memset |
11 | ||
12 | // Need to include windows so that we can capture key presses properly | |
13 | #if defined _WIN32 || defined __CYGWIN__ | |
14 | #include <windows.h> | |
15 | #endif | |
16 | ||
17 | #define USE_SEPARATE_FILES | |
18 | #ifdef USE_SEPARATE_FILES | |
19 | #include "RenderContext.h" | |
20 | #include "I_Drawable.h" | |
21 | #include "GameBoard.h" | |
22 | #ifdef WIN32 | |
23 | #include "CmdPromptHelper.h" | |
24 | #endif | |
25 | #else | |
26 | ||
27 | // RenderContext | |
28 | #ifndef USE_SEPARATE_FILES | |
29 | // Class that acts a canvas for things to draw to | |
30 | class RenderContext { | |
31 | private: | |
32 | int m_width, m_height; // Width and Height of this canvas | |
33 | char* m_renderBuffer; // Array to hold "pixels" of canvas | |
34 | char m_clearChar; // What to clear the array to | |
35 | ||
36 | public: | |
37 | // Empty Constructor | |
38 | RenderContext() : m_width(50), m_height(20), m_clearChar(' ') { | |
39 | m_renderBuffer = new char[m_width * m_height]; | |
40 | } | |
41 | ||
42 | // Construct to specify width and height of canvas | |
43 | RenderContext(int width, int height) : m_width(width), m_height(height), m_clearChar(' ') { | |
44 | m_renderBuffer = new char[m_width * m_height]; | |
45 | } | |
46 | ||
47 | // Destructor | |
48 | ~RenderContext() { | |
49 | if (m_renderBuffer) { | |
50 | delete m_renderBuffer; | |
51 | } | |
52 | } | |
53 | ||
54 | // Get a specific "pixel" on a canvas | |
55 | char getContentAt(int x, int y) { | |
56 | if (((0 <= x) && (x < m_width)) && ((0 <= y) && (y < m_height))) { | |
57 | return m_renderBuffer[(x + (y * m_width))]; | |
58 | } | |
59 | } | |
60 | ||
61 | // Fill a specific "pixel" on the canvas | |
62 | void setContentAt(int x, int y, char val) { | |
63 | if (((0 <= x) && (x < m_width)) && ((0 <= y) && (y < m_height))) { | |
64 | m_renderBuffer[(x + (y * m_width))] = val; | |
65 | } | |
66 | } | |
67 | ||
68 | // Change what this canvas is flooded with when cleared | |
69 | void setClearChar(char clearChar) { | |
70 | m_clearChar = clearChar; | |
71 | } | |
72 | ||
73 | // Paint the canvas to the shell | |
74 | void render() { | |
75 | int row, column; | |
76 | for (row = 0; row < m_height; row++) { | |
77 | for (column = 0; column < m_width; column++) { | |
78 | printf("%c", getContentAt(column, row)); | |
79 | } | |
80 | printf("\n"); | |
81 | } | |
82 | } | |
83 | ||
84 | // Clear the canvas | |
85 | void clear() { | |
86 | memset(m_renderBuffer, m_clearChar, m_width * m_height); | |
87 | } | |
88 | }; | |
89 | #endif | |
90 | ||
91 | // I_Drawable | |
92 | #ifndef USE_SEPARATE_FILES | |
93 | // Interface that allows objects to draw to the canvas | |
94 | class I_Drawable { | |
95 | public: | |
96 | // Virtual function that draws to the canvas | |
97 | virtual void draw(RenderContext&) = 0; | |
98 | }; | |
99 | #endif | |
100 | ||
101 | // Marker | |
102 | #ifndef USE_SEPARATE_FILES | |
103 | // Class representing the cursor that selects objects on a game board | |
104 | class Marker : public I_Drawable { | |
105 | private: | |
106 | int m_xPos, m_yPos; // Position of cursor | |
107 | public: | |
108 | // Empty constructor | |
109 | Marker() : m_xPos(0), m_yPos(0) { | |
110 | } | |
111 | // Parameterized constructor allowing for setting of initial position | |
112 | Marker(int xPos, int yPos) : m_xPos(xPos), m_yPos(yPos) { | |
113 | } | |
114 | // Draw the cursor to the canvas | |
115 | void draw(RenderContext& renderTarget) { | |
116 | ||
117 | // Adjust marker by board spacing | |
118 | // (This is kind of a hack and should be changed) | |
119 | int tmpX, tmpY; | |
120 | tmpX = ((m_xPos * 5) + 1); | |
121 | tmpY = ((m_yPos * 3) + 1); | |
122 | ||
123 | // Set surrounding elements | |
124 | renderTarget.setContentAt(tmpX - 0, tmpY - 1, '-'); | |
125 | renderTarget.setContentAt(tmpX - 1, tmpY - 0, '|'); | |
126 | renderTarget.setContentAt(tmpX - 0, tmpY + 1, '-'); | |
127 | renderTarget.setContentAt(tmpX + 1, tmpY - 0, '|'); | |
128 | } | |
129 | // Get x position | |
130 | int getXPos() { | |
131 | return m_xPos; | |
132 | } | |
133 | // Get y position | |
134 | int getYPos() { | |
135 | return m_yPos; | |
136 | } | |
137 | // Set x position | |
138 | void setXPos(int xPos) { | |
139 | m_xPos = xPos; | |
140 | } | |
141 | // Set y position | |
142 | void setYPos(int yPos) { | |
143 | m_yPos = yPos; | |
144 | } | |
145 | }; | |
146 | #endif | |
147 | ||
148 | // MoveDirection | |
149 | #ifndef USE_SEPARATE_FILES | |
150 | // Enumeration to hold directions a cursor can move | |
151 | typedef enum { | |
152 | MD_UP, | |
153 | MD_DOWN, | |
154 | MD_LEFT, | |
155 | MD_RIGHT | |
156 | } MoveDirection; | |
157 | #endif | |
158 | ||
159 | // GameBoard | |
160 | #ifndef USE_SEPARATE_FILES | |
161 | // Class to represent the board of a game being played | |
162 | class GameBoard : public I_Drawable { | |
163 | private: | |
164 | int m_width, m_height; // Width and height of the board | |
165 | int m_verticalSpacing, m_horizontalSpacing; // Spaces between each element on the board | |
166 | Marker m_marker; // The cursor that will draw on this board | |
167 | int* m_board; // Array of elements on this board | |
168 | ||
169 | // Set a value at a spot on this board | |
170 | // (There is no actual padding on the board, so do not include vertical/horizontal spacing) | |
171 | void setAtPos(int x, int y, int val) { | |
172 | if (((0 <= x) && (x < m_width)) && ((0 <= y) && (x < m_height))) { | |
173 | m_board[(x + (y * m_width))] = val; | |
174 | } | |
175 | } | |
176 | ||
177 | // Actually create the board | |
178 | void generateBoard() { | |
179 | int row, column, randomNumber, valToLeft, valToTop; | |
180 | ||
181 | // Iterate over all rows and columns | |
182 | for (row = 0; row < m_height; row++) { | |
183 | for (column = 0; column < m_width; column++) { | |
184 | // Get the previous elements | |
185 | valToLeft = getAtPos(column - 1, row); | |
186 | valToTop = getAtPos(column, row - 1); | |
187 | ||
188 | // Generate random numbers until we have one | |
189 | // that is not the same as an adjacent element | |
190 | do { | |
191 | randomNumber = (2 + (rand() % 7)); | |
192 | } while ((valToLeft == randomNumber) || (valToTop == randomNumber)); | |
193 | setAtPos(column, row, randomNumber); | |
194 | } | |
195 | } | |
196 | } | |
197 | ||
198 | public: | |
199 | // Empty Constructor | |
200 | GameBoard() : m_width(10), m_height(10), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) { | |
201 | m_board = new int[m_width * m_height]; | |
202 | generateBoard(); | |
203 | } | |
204 | ||
205 | // Constructor that takes a width and height of a board | |
206 | GameBoard(int width, int height) : m_width(width), m_height(height), m_verticalSpacing(5), m_horizontalSpacing(3), m_marker(Marker()) { | |
207 | m_board = new int[m_width * m_height]; | |
208 | generateBoard(); | |
209 | } | |
210 | ||
211 | // Destructor | |
212 | ~GameBoard() { | |
213 | if (m_board) { | |
214 | delete m_board; | |
215 | } | |
216 | } | |
217 | ||
218 | // Function to get an element on the board | |
219 | // I would say overload operator() but it's often overkill | |
220 | // and can lead to hard to find bugs | |
221 | int getAtPos(int x, int y) { | |
222 | if (((0 <= x) && (x < m_width)) && ((0 <= y) && (x < m_height))) { | |
223 | return m_board[(x + (y * m_width))]; | |
224 | } | |
225 | } | |
226 | ||
227 | // Function to draw to the canvas | |
228 | void draw(RenderContext& renderTarget) { | |
229 | int row, column; | |
230 | char buffer[8]; | |
231 | ||
232 | // Iterate over every element | |
233 | for (row = 0; row < m_height; row++) { | |
234 | for (column = 0; column < m_width; column++) { | |
235 | ||
236 | // Convert the integer to a char | |
237 | sprintf(buffer, "%d", getAtPos(column, row)); | |
238 | ||
239 | // Set the canvas "pixel" to the char at the | |
240 | // desired position including the padding | |
241 | renderTarget.setContentAt( | |
242 | ((column * m_verticalSpacing) + 1), | |
243 | ((row * m_horizontalSpacing) + 1), | |
244 | buffer[0]); | |
245 | } | |
246 | } | |
247 | ||
248 | // Draw the marker | |
249 | m_marker.draw(renderTarget); | |
250 | } | |
251 | ||
252 | // Function to move the cursor around depending on input | |
253 | // (Does not allow the cursor to move off of the board) | |
254 | void handleInput(MoveDirection moveDirection) { | |
255 | switch (moveDirection) { | |
256 | case MD_UP: | |
257 | if (m_marker.getYPos() > 0) { | |
258 | m_marker.setYPos(m_marker.getYPos() - 1); | |
259 | } | |
260 | break; | |
261 | case MD_DOWN: | |
262 | if (m_marker.getYPos() < m_height - 1) { | |
263 | m_marker.setYPos(m_marker.getYPos() + 1); | |
264 | } | |
265 | - | // Get the x position of the marker on the board as it relates |
265 | + | |
266 | - | // to its modified position on the board |
266 | + | |
267 | - | int getMarkerX() { |
267 | + | |
268 | - | return m_marker.getXPos() * m_horizontalSpacing; |
268 | + | |
269 | } | |
270 | break; | |
271 | - | // Get the y position of the marker on the board as it relates |
271 | + | |
272 | - | // to its modified position on the board |
272 | + | |
273 | - | int getMarkerY() { |
273 | + | |
274 | - | return m_marker.getYPos() * m_verticalSpacing; |
274 | + | |
275 | break; | |
276 | } | |
277 | } | |
278 | ||
279 | // Get the width of the board | |
280 | int getWidth() { | |
281 | return m_width; | |
282 | } | |
283 | ||
284 | // Get the height of the board | |
285 | int getHeight() { | |
286 | return m_height; | |
287 | } | |
288 | }; | |
289 | #endif | |
290 | ||
291 | // CmdPromptHelper | |
292 | #ifndef USE_SEPARATE_FILES | |
293 | // This class is only useful if we are building on windows | |
294 | #ifdef WIN32 | |
295 | ||
296 | // Class to help with drawing on the windows command prompt | |
297 | class CmdPromptHelper { | |
298 | private: | |
299 | DWORD inMode; // Attributes of std::in before we change them | |
300 | DWORD outMode; // Attributes of std::out before we change them | |
301 | HANDLE hstdin; // Handle to std::in | |
302 | HANDLE hstdout; // Handle to std::out | |
303 | public: | |
304 | // Empty constructor | |
305 | CmdPromptHelper() { | |
306 | // Get a handle to input of prompt | |
307 | hstdin = GetStdHandle(STD_INPUT_HANDLE); | |
308 | if ((hstdin == INVALID_HANDLE_VALUE) | |
309 | || !GetConsoleMode(hstdin, &inMode) | |
310 | || !SetConsoleMode(hstdin, 0)) { | |
311 | return; | |
312 | } | |
313 | ||
314 | // Get handle for output of prompt | |
315 | hstdout = GetStdHandle(STD_OUTPUT_HANDLE); | |
316 | if ((hstdout == INVALID_HANDLE_VALUE) | |
317 | || !GetConsoleMode(hstdout, &outMode)) { | |
318 | return; | |
319 | } | |
320 | } | |
321 | ||
322 | - | return inrec.Event.KeyEvent.wVirtualKeyCode; |
322 | + | |
323 | void reset() { | |
324 | SetConsoleMode(hstdin, inMode); | |
325 | } | |
326 | ||
327 | // See what key is pressed by the user and return it | |
328 | WORD getKeyPress() { | |
329 | if (hstdin != INVALID_HANDLE_VALUE) { | |
330 | DWORD count; | |
331 | INPUT_RECORD inrec; | |
332 | ||
333 | - | void clearScreen(int posX, int posY) { |
333 | + | |
334 | ReadConsoleInput(hstdin, &inrec, 1, &count); | |
335 | ||
336 | // Return key | |
337 | if (inrec.Event.KeyEvent.bKeyDown) { | |
338 | - | COORD homeCoord = {posX, posY}; // Where to put the cursor when we're done |
338 | + | return inrec.Event.KeyEvent.wVirtualKeyCode; |
339 | } | |
340 | ||
341 | // Flush input | |
342 | FlushConsoleInputBuffer(hstdin); | |
343 | } else { | |
344 | return 0; | |
345 | } | |
346 | } | |
347 | ||
348 | // Flood the console with empty space so that we can | |
349 | // simulate single buffering (I have no idea how to double buffer this) | |
350 | void clearScreen() { | |
351 | if (hstdout != INVALID_HANDLE_VALUE) { | |
352 | CONSOLE_SCREEN_BUFFER_INFO csbi; | |
353 | DWORD cellCount; // How many cells to paint | |
354 | DWORD count; // How many we painted | |
355 | COORD homeCoord = {0, 0}; // Where to put the cursor when we're done | |
356 | ||
357 | // Get console info | |
358 | if (!GetConsoleScreenBufferInfo(hstdout, &csbi)) { | |
359 | return; | |
360 | } | |
361 | ||
362 | // Get cell count | |
363 | cellCount = csbi.dwSize.X * csbi.dwSize.Y; | |
364 | ||
365 | // Fill the screen with spaces | |
366 | FillConsoleOutputCharacter( | |
367 | hstdout, | |
368 | (TCHAR) ' ', | |
369 | cellCount, | |
370 | homeCoord, | |
371 | &count | |
372 | ); | |
373 | ||
374 | // Set cursor position | |
375 | SetConsoleCursorPosition(hstdout, homeCoord); | |
376 | } | |
377 | } | |
378 | }; | |
379 | #endif | |
380 | ||
381 | #endif | |
382 | ||
383 | #endif | |
384 | // Entry point of program | |
385 | int main() { | |
386 | // Create a game board | |
387 | GameBoard gb(5, 5); | |
388 | ||
389 | // Create something to render to | |
390 | RenderContext rc(25, 15); | |
391 | rc.setClearChar('.'); | |
392 | rc.clear(); | |
393 | ||
394 | // If on windows, we can animate | |
395 | #ifdef WIN32 | |
396 | // Make a helper | |
397 | CmdPromptHelper cph; | |
398 | ||
399 | // Variable to hold key presses | |
400 | WORD key; | |
401 | ||
402 | // Play Game | |
403 | do { | |
404 | // Draw the board to the render object | |
405 | gb.draw(rc); | |
406 | ||
407 | // Draw the render object | |
408 | rc.render(); | |
409 | ||
410 | // Get Key Press | |
411 | - | cph.clearScreen(gb.getMarkerX(), gb.getMarkerY()); |
411 | + | |
412 | ||
413 | // Handle the key press | |
414 | switch (key) { | |
415 | case VK_UP: | |
416 | gb.handleInput(MD_UP); | |
417 | break; | |
418 | case VK_DOWN: | |
419 | gb.handleInput(MD_DOWN); | |
420 | break; | |
421 | case VK_LEFT: | |
422 | gb.handleInput(MD_LEFT); | |
423 | break; | |
424 | case VK_RIGHT: | |
425 | gb.handleInput(MD_RIGHT); | |
426 | break; | |
427 | } | |
428 | ||
429 | // Clear Screen | |
430 | rc.clear(); | |
431 | cph.clearScreen(); | |
432 | ||
433 | } while (key != VK_ESCAPE); | |
434 | ||
435 | // Reset console | |
436 | cph.reset(); | |
437 | ||
438 | // If we aren't on windows, we can't animate and have | |
439 | // to write OS specific code to handle key presses | |
440 | #else | |
441 | ||
442 | // Variable to hold key presses | |
443 | char key; | |
444 | ||
445 | // Play Game | |
446 | do { | |
447 | // Draw the board to the render object | |
448 | gb.draw(rc); | |
449 | ||
450 | // Draw the render object | |
451 | rc.render(); | |
452 | ||
453 | // Get Key Press | |
454 | key = getchar(); | |
455 | ||
456 | } while (key != 0); | |
457 | ||
458 | #endif | |
459 | } | |
460 | ||
461 | // EoF |