1 /***************************************************************************
2  * Brutal Chess
3  * http://brutalchess.sf.net
4  *
5  * File : gamecore.cpp
6  * Authors : Mike Cook, Joe Flint, Neil Pankey
7  **************************************************************************/
8 
9 #include "boardtheme.h"
10 #include "chessgame.h"
11 #include "chessplayer.h"
12 #include "config.h"
13 #include "fontloader.h"
14 #include "gamecore.h"
15 #include "menu.h"
16 #include "menuitem.h"
17 #include "objfile.h"
18 #include "options.h"
19 #include "SDL.h"
20 #include "SDL_opengl.h"
21 #include "SDL_thread.h"
22 #include "texture.h"
23 
24 #include <cmath>
25 #include <iostream>
26 
27 using std::cout;
28 using std::cerr;
29 using std::endl;
30 
31 // Menu string constants
32 static const string AI_DIFFICULTY_EASY = "Easy";
33 static const string AI_DIFFICULTY_MEDIUM = "Medium";
34 static const string AI_DIFFICULTY_HARD = "Hard";
35 
36 static const string PLAYER_BRUTAL = "Brutal";
37 static const string PLAYER_HUMAN = "Human";
38 static const string PLAYER_RANDOM = "Random";
39 
40 GameCore * GameCore::m_instance = 0;
41 
get_Instance()42 GameCore * GameCore::get_Instance()
43 {
44 	if (m_instance == 0)
45 		m_instance = new GameCore;
46 	return m_instance;
47 }
48 
destroy()49 void GameCore::destroy()
50 {
51 	delete m_theme;
52 	m_theme = 0;
53 	delete m_set;
54 	m_set;
55 	delete this;
56 }
57 
init(const ChessGame & cg,BoardTheme * bt,PieceSet * ps)58 void GameCore::init(const ChessGame & cg, BoardTheme * bt, PieceSet * ps)
59 {
60 	m_game	= cg;
61 	m_theme	= bt;
62 	m_set	= ps;
63 }
64 
load()65 bool GameCore::load()
66 {
67 	m_options = Options::getInstance();
68 
69 	m_set->load();
70 	m_theme->load();
71 	m_game.newGame();
72 	m_game.startGame();
73 	m_rotatey = 0.0;
74 	m_isWaitingForPromotion = false;
75 	m_rotate = false;
76 	m_loaded = true;
77 	m_suggestedwhiteplayer = m_options->getPlayer1String();
78 	m_suggestedblackplayer = m_options->getPlayer2String();
79 	return true;
80 }
81 
loadGL()82 bool GameCore::loadGL()
83 {
84 	if(m_loadthread) {
85 		SDL_WaitThread(m_loadthread, NULL);
86 		m_loadthread = 0;
87 	}
88 
89 	m_set->loadGL();
90 	m_theme->loadGL();
91 
92 	m_defaultcur = SDL_GetCursor();
93 	char blank[64];
94 	for(int i = 0; i < 64; i++) {
95 		blank[i] = 0;
96 	}
97 
98 	m_blankcur = SDL_CreateCursor((Uint8*)blank, (Uint8*)blank, 8, 8, 0, 0);
99 	m_glloaded = true;
100 
101 	if(!m_thinkthread) {
102 		spawnThinkThread();
103 	}
104 
105 	return true;
106 }
107 
unloadGL()108 void GameCore::unloadGL()
109 {
110 	if(!m_glloaded) {
111 		return;
112 	}
113 
114 	m_set->unloadGL();
115 	m_theme->unloadGL();
116 
117 	SDL_FreeCursor(m_blankcur);
118 
119 	m_glloaded = false;
120 }
121 
draw()122 void GameCore::draw()
123 {
124 	if(m_loaded) {
125 		if(!m_glloaded) {
126 			loadGL();
127 		}
128 
129 		toBoardSpace();
130 
131 		// Fix the lighting so it rotates with the board (same as shadow light)
132 		GLfloat light_position[4];
133 		light_position[0] = 10;
134 		light_position[1] = 12;
135 		light_position[2] = 6;
136 		light_position[3] = 1;
137 		//glLightfv(GL_LIGHT0, GL_POSITION, light_position);
138 
139         if(m_options->reflections) {
140 			drawReflections();
141 		}
142 
143 		// Blend the draw reflections with the board.
144 		glEnable( GL_BLEND );
145 		glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );
146 		m_theme->draw(m_game.getState());
147 		glDisable( GL_BLEND );
148 
149         if(m_options->shadows) {
150 			projectShadows();
151 		}
152 
153 		m_set->draw(m_game.getState());
154 
155 
156 		// Draw a spinning pawn (like the loading screen) if we are waiting on the player
157 		if(!m_game.getCurrentPlayer()->isHuman()) {
158 			glLoadIdentity();
159 
160             double height = static_cast<double>(m_options->getResolutionHeight());
161             double width = static_cast<double>(m_options->getResolutionWidth());
162             double aspectRatioDiff = (height/width)  - 0.75;
163             glTranslated(33.5 - 50.0*aspectRatioDiff, -23.5, -106);
164 
165 			glRotatef(SDL_GetTicks()/3.5,0,1,0);
166 			glRotatef(30,0,0,1);
167 			glTranslatef(0, -3.5, 0);
168 
169 			// Make sure we can see this above the board
170 			glDisable(GL_DEPTH_TEST);
171 			Piece p(m_game.getCurrentPlayer()->getColor(), Piece::PAWN);
172 			glScalef(7.0, 7.0, 7.0);
173 			// Slightly transparent
174 			m_set->drawPiece(&p, 0.9, false);
175 			glScalef(1/7.0, 1/7.0, 1/7.0);
176 			glEnable(GL_DEPTH_TEST);
177 		}
178 
179 		if (m_menu.isActive()) {
180 			m_menu.draw();
181         }
182 
183         // See if the game has ended, if it has increment a timer that runs
184         // to allow the player to see what happened before displaying the
185         // end game menu.
186         if (m_endgametimer.started()) {
187             m_endgametimer++;
188         } else if (m_endgametimer.done()) {
189             buildMenu();
190             m_menu.pushOptionsSet("Game Over");
191             string condition;
192             if (m_game.getBoard().isCheckMate(Piece::BLACK)) {
193                 condition = "Checkmate - White Wins";
194             }
195             else if (m_game.getBoard().isCheckMate(Piece::WHITE)) {
196                 condition = "Checkmate - Black Wins";
197             }
198             else {
199                 condition = "Stalemate";
200             }
201             m_menu.setHeader(condition);
202             m_menu.activate();
203             m_endgametimer.resetDone();
204         }
205 
206     } else {
207 		glLoadIdentity();
208 		drawLoadingScreen();
209 		// Extra delay during the loading screen to avoid choking the CPU
210 		SDL_Delay(25);
211 	}
212 }
213 
handleEvent(SDL_Event & e)214 bool GameCore::handleEvent(SDL_Event& e)
215 {
216 	if(!m_loaded)
217 		return false;
218 
219     // Needs to handle mouse, keyboard, and some application events
220 	// Most window level tasks will be handled in main
221 	if(m_menu.handleEvent(e)) {
222 		return true;
223 	}
224 
225 	if (e.type == SDL_MOUSEMOTION) {
226 		if(m_rotate) {
227 			GLfloat viewport[4];
228 			glGetFloatv(GL_VIEWPORT, viewport);
229 			m_rotatey += 360*(e.motion.xrel / viewport[2]);
230 			m_rotatex += 360*(e.motion.yrel / viewport[3]);
231 			if(m_rotatex < -42)
232 				m_rotatex = -42;
233 			if(m_rotatex > 48)
234 				m_rotatex = 48;
235 			return true;
236 		}
237 		m_mousex = e.motion.x;
238 		m_mousey = e.motion.y;
239 		updateMouseBoardPos();
240 		m_theme->hoverPosition(m_mousepos);
241 		m_set->mousePosition(m_mouseboardx, m_mouseboardy);
242 		m_set->hoverPosition(m_game.getState(), m_mousepos);
243 		return true;
244 	}
245 	else if (e.type == SDL_MOUSEBUTTONDOWN) {
246 		updateMouseBoardPos();
247 		bool needMove = m_game.getCurrentPlayer()->needMove();
248 		if (e.button.button == SDL_BUTTON_RIGHT) {
249 			// If a piece has already been picked up, release the piece.
250 			if(m_firstclick.isValid() && needMove) {
251 				SDL_SetCursor(m_defaultcur);
252 				m_set->deselectPosition();
253 				m_firstclick.invalidate();
254 			} else {
255 				m_rotate = true;
256 				m_mousex = e.button.x;
257 				m_mousey = e.button.y;
258 				m_theme->hoverPosition(BoardPosition());
259 				m_set->hoverPosition(m_game.getState(), BoardPosition());
260 				SDL_ShowCursor(SDL_DISABLE);
261 				SDL_WM_GrabInput(SDL_GRAB_ON);
262 			}
263 		}
264 		else if (!needMove) {
265 		} else if (e.button.button == SDL_BUTTON_LEFT) {
266 			if(m_rotate)
267 				return false;
268 
269 			// Check to see if we're waiting for the player to select a piece to
270 			// promote his pawn to.
271 			if (this->isWaitingForPromotion()) {
272 				Piece::Type t = getPromotionSelection(m_mousepos);
273 				if (t == Piece::PAWN) {
274 					return false;
275 				}
276 				BoardMove bm(m_firstclick, m_secondclick, m_game.getBoard().getPiece(m_firstclick));
277 				bm.setPromotion(t);
278 				if (m_game.getCurrentPlayer()->needMove()) {
279 					m_game.getCurrentPlayer()->sendMove(bm);
280 				}
281 				m_set->drawPromotionSelector(false);
282 				this->setIsWaitingForPromotion(false);
283 				return true;
284 			}
285 
286 			// Check to see if we've clicked yet
287 			if (m_firstclick.isValid()) {
288 				if(m_firstclick == m_mousepos) {
289 					// Clicked on the start position, unselect
290 					SDL_SetCursor(m_defaultcur);
291 					m_set->deselectPosition();
292 					m_firstclick.invalidate();
293 				}
294 				else {
295 					// We've already clicked once, so save this new click position and
296 					// make the move from m_firstclick to the new position
297 					BoardMove bm(m_firstclick, m_mousepos, m_game.getBoard().getPiece(m_firstclick));
298 					if (bm.needPromotion()) {
299 						m_secondclick = m_mousepos;
300 						if (m_game.getCurrentPlayer()->isHuman()) {
301 							m_set->drawPromotionSelector(true);
302 							this->setIsWaitingForPromotion(true);
303 							SDL_SetCursor(m_defaultcur);
304 						}
305 						else {
306 							// TODO - Get promotion from AI
307 						}
308 					}
309 					else if (m_game.getCurrentPlayer()->needMove()) {
310 						// The move doesn't need promotion, so simply send it off
311 						// to the current player.
312 						m_game.getCurrentPlayer()->sendMove(bm);
313 					}
314 				}
315 			}
316 			else {
317 				// We haven't clicked yet so set m_firstclick to this click's position
318 				if(m_mousepos.isValid() && m_game.getBoard().isOccupied(m_mousepos)) {
319 					// Make sure player is selecting his own piece
320 					if(m_game.getTurn() == m_game.getBoard().getPiece(m_mousepos)->color()) {
321 						SDL_SetCursor(m_blankcur);
322 						m_firstclick = m_mousepos;
323 						m_set->selectPosition(m_mousepos);
324 					}
325 				}
326 			}
327 		}
328 	}
329 	else if (e.type == SDL_MOUSEBUTTONUP) {
330 		if (e.button.button == SDL_BUTTON_RIGHT && m_rotate) {
331 			m_rotate = false;
332 			SDL_ShowCursor(SDL_ENABLE);
333 			SDL_WM_GrabInput(SDL_GRAB_OFF);
334 			SDL_WarpMouse(m_mousex, m_mousey);
335 		}
336 	}
337 	else if (e.type == SDL_KEYDOWN) {
338 			if (e.key.keysym.sym == SDLK_ESCAPE) {
339 				buildMenu();
340 				if (m_menu.isActive())
341 					m_menu.deactivate();
342 				else {
343 					// If a piece is picked up, drop it before showing the menu
344 					if(m_firstclick.isValid()) {
345 						SDL_SetCursor(m_defaultcur);
346 						m_set->deselectPosition();
347 						m_firstclick.invalidate();
348 					}
349 					m_menu.activate();
350 				}
351 			}
352 	}
353 	else if (e.type == SDL_USEREVENT) {
354 		if(e.user.code == 0) {
355 			// Received notification that the player is done thinking
356 			ChessPlayer *player = (ChessPlayer*)e.user.data1;
357 			//m_set->animateMove(player->getMove());
358 			if(m_game.tryMove(player->getMove())) {
359 				m_game.getCurrentPlayer()->opponentMove(player->getMove(), m_game.getState());
360 				SDL_SetCursor(m_defaultcur);
361 				m_set->deselectPosition();
362 				m_firstclick.invalidate();
363 			}
364 			SDL_WaitThread(m_thinkthread, NULL);
365 			m_thinkthread = NULL;
366 
367 			// Only temporary, really want to do this after animation is done
368 	    	if (!(m_game.getBoard().containsCheckMate() || m_game.getState().isDraw())) {
369 				spawnThinkThread();
370 			} else {
371                 m_endgametimer = Timer(Timer::LINEAR);
372                 m_endgametimer.setDuration(1.0);
373                 m_endgametimer.start();
374 			}
375 		}
376 		else if (e.user.code == Menu::eQUIT) {
377 			SDL_Event quitevent;
378 			quitevent.type = SDL_QUIT;
379 			SDL_PushEvent(&quitevent);
380 		}
381 		else if (e.user.code == Menu::eREFLECTTOG) {
382             m_options->reflections = !m_options->reflections;
383 		}
384 		else if (e.user.code == Menu::eSHADOWTOG) {
385             m_options->shadows = !m_options->shadows;
386 		}
387 		else if (e.user.code == Menu::eHISTORYARROWSTOG) {
388 			m_theme->toggleHistoryArrows();
389 		}
390 		else if (e.user.code == Menu::eBBRUTALPLYCHANGED) {
391 			// Set the AI ply depth based on the ai difficulty string
392 			string difficulty = m_blackbrutalplychoices->getCurrentChoice();
393 			if (AI_DIFFICULTY_EASY == difficulty) {
394                 m_options->brutalplayer2ply = EASY;
395 			}
396 			else if (AI_DIFFICULTY_MEDIUM == difficulty) {
397 				m_options->brutalplayer2ply = MEDIUM;
398 			}
399 			else if (AI_DIFFICULTY_HARD == difficulty) {
400 				m_options->brutalplayer2ply = HARD;
401 			}
402 			BrutalPlayer* player = dynamic_cast<BrutalPlayer*>(m_game.getPlayer2());
403 			if (player) {
404 				player->setPly(m_options->brutalplayer2ply);
405 			}
406 		}
407 		else if (e.user.code == Menu::eBLACKPLAYERCHANGED) {
408 			m_suggestedblackplayer = m_blackplayerchoices->getCurrentChoice();
409 			m_blackbrutalplychoices->setCollapsed(PLAYER_BRUTAL != m_suggestedblackplayer);
410 		}
411 		else if (e.user.code == Menu::eWBRUTALPLYCHANGED) {
412 			// Set the AI ply depth based on the ai difficulty string
413 			string difficulty = m_whitebrutalplychoices->getCurrentChoice();
414 			if (AI_DIFFICULTY_EASY == difficulty) {
415 				m_options->brutalplayer1ply = EASY;
416 			}
417 			else if (AI_DIFFICULTY_MEDIUM == difficulty) {
418 				m_options->brutalplayer1ply = MEDIUM;
419 			}
420 			else if (AI_DIFFICULTY_HARD == difficulty) {
421 				m_options->brutalplayer1ply = HARD;
422 			}
423 			BrutalPlayer* player = dynamic_cast<BrutalPlayer*>(m_game.getPlayer1());
424 			if (player) {
425 				player->setPly(m_options->brutalplayer1ply);
426 			}
427 		}
428 		else if (e.user.code == Menu::eWHITEPLAYERCHANGED) {
429 			m_suggestedwhiteplayer = m_whiteplayerchoices->getCurrentChoice();
430 			m_whitebrutalplychoices->setCollapsed(PLAYER_BRUTAL != m_suggestedwhiteplayer);
431 		}
432 		else if (e.user.code == Menu::eSTARTNEWGAME) {
433 			ChessPlayer * whiteplayer = PlayerFactory(m_suggestedwhiteplayer);
434 			BrutalPlayer* brutalplayer = dynamic_cast<BrutalPlayer*>(whiteplayer);
435 			if (brutalplayer) {
436 				brutalplayer->setPly(m_options->brutalplayer1ply);
437 			}
438 			whiteplayer->setIsWhite(true);
439 			ChessPlayer * blackplayer = PlayerFactory(m_suggestedblackplayer);
440 			brutalplayer = dynamic_cast<BrutalPlayer*>(blackplayer);
441 			if (brutalplayer) {
442 				brutalplayer->setPly(m_options->brutalplayer2ply);
443 			}
444 			blackplayer->setIsWhite(false);
445 			m_game.setPlayer1(whiteplayer);
446 			m_game.setPlayer2(blackplayer);
447 			m_game.newGame();
448 			if (m_thinkthread) {
449 				SDL_KillThread(m_thinkthread);
450 			}
451 			spawnThinkThread();
452 			SDL_Event backEvent;
453 			backEvent.type = SDL_USEREVENT;
454 			backEvent.user.code = Menu::eBACK;
455 			SDL_PushEvent(&backEvent);
456 			SDL_Event backEvent2;
457 			backEvent2.type = SDL_USEREVENT;
458 			backEvent2.user.code = Menu::eBACK;
459 			SDL_PushEvent(&backEvent2);
460 			SDL_Event backEvent3;
461 			backEvent3.type = SDL_USEREVENT;
462 			backEvent3.user.code = Menu::eBACK;
463 			SDL_PushEvent(&backEvent3);
464 		}
465 	}
466 	return false;
467 }
468 
preload()469 bool GameCore::preload()
470 {
471 	if(m_loadpawn.load("../models/pawn.obj")) {
472 		// add debugging
473 	} else if(m_loadpawn.load(MODELS_DIR + string("pawn.obj"))) {
474 		// add debugging
475 	} else {
476 		cerr << "Failed to load pawn." << endl;
477 		return false;
478 	}
479 
480 	m_loadpawn.findNorms();
481 	m_loadpawn.setScale(m_loadpawn.scale()*12);
482 
483 	if(FontLoader::loadFont("sans", "../fonts/ZEROES__.TTF", 32)) {
484 		// add debugging
485 	} else if(FontLoader::loadFont("sans", FONTS_DIR + string("ZEROES__.TTF"), 32)) {
486 		// add debugging
487 	} else {
488 		cerr << "Failed to load fonts." << endl;
489 		return false;
490 	}
491 
492 	if(m_logotexture.load("../art/brutalchesslogo.png")) {
493 		// add debugging
494 	} else if(m_logotexture.load(ART_DIR + string("brutalchesslogo.png"))) {
495 		// add debugging
496 	} else {
497 		cerr << "Failed to load logo" << endl;
498 		return false;
499 	}
500 
501 	m_logotexture.loadGL();
502 	m_preloaded = true;
503 	return true;
504 }
505 
drawLoadingScreen()506 void GameCore::drawLoadingScreen()
507 {
508 	if(!m_preloaded)
509 		preload();
510 	glTranslated(0,0,-106);
511 
512 	// Drawing the Brutal Chess Logo
513 	m_logotexture.use();
514 	glEnable(GL_TEXTURE_2D);
515 	glDisable(GL_LIGHTING);
516 	glEnable(GL_BLEND);
517 	glColor3f(1.0, 1.0, 1.0);
518 	glBegin(GL_QUADS);
519 		glNormal3d(0,0,-1);
520 		glTexCoord2d(0.0,0.0);
521 		glVertex3f(-35.0,25.0,0.0);
522 		glTexCoord2d(0.0,1.0);
523 		glVertex3f(-35.0,-10.0,0.0);
524 		glTexCoord2d(1.0,1.0);
525 		glVertex3f(35.0,-10.0,0.0);
526 		glTexCoord2d(1.0,0.0);
527 		glVertex3f(35.0,25.0,0.0);
528 	glEnd();
529 	glDisable(GL_BLEND);
530 	glEnable(GL_LIGHTING);
531 	glDisable(GL_TEXTURE_2D);
532 
533 	// Alpha value to create a pulsing effect
534 	double c = 0.6+0.4*cos(0.15*SDL_GetTicks()*3.1415/180.0);
535 
536 	// Drawing the Loading text
537 	glEnable(GL_BLEND);
538 	glColor4f(1.0, 1.0, 1.0, c);
539 	glTranslated(28, -18, 0);
540 	glScaled(1/10.0, 1/10.0, 1/10.0);
541 	FontLoader::print(-28,-7,"Loading...");
542 	glScaled(10, 10, 10);
543 	glDisable(GL_BLEND);
544 
545 	// Spinning pawn
546 	glRotatef(SDL_GetTicks()/2.5,0,1,0);
547 	glRotatef(30,0,0,1);
548 
549 	m_loadpawn.draw();
550 }
551 
projectShadows()552 void GameCore::projectShadows()
553 {
554 	glPushAttrib(GL_ENABLE_BIT | GL_DEPTH_BUFFER_BIT | GL_COLOR_BUFFER_BIT);
555 
556 	// Generate project matrix for planar shadows
557 	GLfloat shadowMat[4][4];
558 	GLfloat groundplane[4];
559 
560 	// Specified in board space, y is up
561 	groundplane[0] = 0;
562 	groundplane[1] = 1;
563 	groundplane[2] = 0;
564 	groundplane[3] = 0;
565 	GLfloat lightpos[4];
566 	lightpos[0] = 6;
567 	lightpos[1] = 12;
568 	lightpos[2] = 10;
569 	// 1, since this is a point light
570 	lightpos[3] = 1;
571 
572 	GLfloat mat[16];
573 
574 	for(int i = 0; i < 16; i++)
575 		mat[i] = 0;
576 
577 	mat[0] = lightpos[0];
578 	mat[4] = lightpos[1];
579 	mat[8] = lightpos[2];
580 	mat[12] = lightpos[3];
581 
582 	glPushMatrix();
583 	glLoadIdentity();
584 	glMultMatrixf(mat);
585 	glRotated(m_rotatex, 1, 0, 0);
586 	glRotated(m_rotatey, 0, 1, 0);
587 	glGetFloatv(GL_MODELVIEW_MATRIX, mat);
588 	glPopMatrix();
589 
590 	lightpos[0] = mat[0] + 4;
591 	lightpos[2] = mat[8] - 4;
592 
593 	GLfloat dot;
594 
595 	dot = groundplane[0] * lightpos[0] +
596 		groundplane[1] * lightpos[1] +
597 		groundplane[2] * lightpos[2] +
598 		groundplane[3] * lightpos[3];
599 
600 	shadowMat[0][0] = dot - lightpos[0] * groundplane[0];
601 	shadowMat[1][0] = 0.f - lightpos[0] * groundplane[1];
602 	shadowMat[2][0] = 0.f - lightpos[0] * groundplane[2];
603 	shadowMat[3][0] = 0.f - lightpos[0] * groundplane[3];
604 
605 	shadowMat[0][1] = 0.f - lightpos[1] * groundplane[0];
606 	shadowMat[1][1] = dot - lightpos[1] * groundplane[1];
607 	shadowMat[2][1] = 0.f - lightpos[1] * groundplane[2];
608 	shadowMat[3][1] = 0.f - lightpos[1] * groundplane[3];
609 
610 	shadowMat[0][2] = 0.f - lightpos[2] * groundplane[0];
611 	shadowMat[1][2] = 0.f - lightpos[2] * groundplane[1];
612 	shadowMat[2][2] = dot - lightpos[2] * groundplane[2];
613 	shadowMat[3][2] = 0.f - lightpos[2] * groundplane[3];
614 
615 	shadowMat[0][3] = 0.f - lightpos[3] * groundplane[0];
616 	shadowMat[1][3] = 0.f - lightpos[3] * groundplane[1];
617 	shadowMat[2][3] = 0.f - lightpos[3] * groundplane[2];
618 	shadowMat[3][3] = dot - lightpos[3] * groundplane[3];
619 
620 	glPushMatrix();
621 
622 	// Apply the project matrix
623 	glMultMatrixf((GLfloat*)shadowMat);
624 
625   	glDisable(GL_LIGHTING);
626 
627 	// Only want to write to the stencil buffer
628 	glDisable(GL_DEPTH_TEST);
629 	glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
630 
631 	// Write a 1 to where the shadows should appear
632 	glEnable(GL_STENCIL_TEST);
633 	glStencilOp(GL_REPLACE, GL_REPLACE, GL_REPLACE);
634 	glStencilFunc(GL_ALWAYS, 1, 0xffffffff);
635 	m_set->draw(m_game.getState());
636 
637 	// Only want to draw where the stencil buffer is 1
638 	glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
639 	glStencilFunc(GL_EQUAL, 1, 0xffffffff);
640 	glPopMatrix();
641 
642 	// Prevent depth buffer glitch
643 	glTranslatef(0, 0.01, 0);
644 
645 	// Draw the actual shadow, a black 50% alpha polygon
646 	glEnable(GL_BLEND);
647 	glDisable(GL_TEXTURE_2D);
648 	glColor4f(0, 0, 0, 0.5);
649 	glBegin( GL_QUADS );
650 	glNormal3d( 0, 7, 0 );
651 	glVertex3d( -0.1, 0,  0.1 );
652 	glVertex3d( 8.1, 0, 0.1 );
653 	glVertex3d( 8.1, 0, -8.1 );
654 	glVertex3d( -0.1, 0,  -8.1 );
655 	glEnd();
656 
657 	// Restore OpenGL state
658 	glTranslatef(0, -0.01, 0);
659 	glPopAttrib();
660 }
661 
drawReflections()662 void GameCore::drawReflections()
663 {
664 	// This plane is drawn and then erased. The purpose is to obtain the mouse
665 	// coordinates, and set the stencil buffer for the reflections
666 	// It is exactly the same size as the board, and is in exactly the same spot
667 	glDisable( GL_DEPTH_TEST );
668 	glEnable( GL_STENCIL_TEST );
669 	glStencilOp( GL_REPLACE, GL_REPLACE, GL_REPLACE );
670 	glStencilFunc( GL_ALWAYS, 1, 0xffffffff );
671 
672 	glBegin( GL_QUADS );
673 		glNormal3d( 0, 7, 0 );
674 		glVertex3d( 0.0, 0.0,  0.0 );
675 		glVertex3d( 8.0, 0.0,  0.0 );
676 		glVertex3d( 8.0, 0.0, -8.0 );
677 		glVertex3d( 0.0, 0.0, -8.0 );
678 	glEnd();
679 
680 	glColorMask( GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE );
681 	glClear( GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT );
682 
683 	// Only draw if the stencil buffer has a 1 here
684 	glEnable( GL_DEPTH_TEST );
685 	glStencilFunc( GL_EQUAL, 1, 0xffffffff );
686 	glStencilOp( GL_KEEP, GL_KEEP, GL_KEEP );
687 
688 	GLfloat light_position[4];
689 	glGetLightfv(GL_LIGHT0, GL_POSITION, light_position);
690 
691 	// Invert over the board plane
692 	glScalef( 1, -1, 1 );
693 	glCullFace(GL_FRONT);
694 
695 	glLightfv(GL_LIGHT1, GL_POSITION, light_position);
696 	glEnable(GL_LIGHT1);
697 	glDisable(GL_LIGHT0);
698 
699 	// Draw the reflected pieces
700 	m_set->draw(m_game.getState());
701 
702 	// Return to normal (not inverted)
703 	glCullFace(GL_BACK);
704 	glScalef( 1, -1, 1 );
705 	glEnable(GL_LIGHT0);
706 	glDisable(GL_LIGHT1);
707 	glDisable( GL_STENCIL_TEST );
708 	glClear( GL_STENCIL_BUFFER_BIT );
709 }
710 
buildMenu()711 void GameCore::buildMenu()
712 {
713 	static bool built = false;
714 	if(!built) {
715 	/* Main
716 		New game
717 		Options ->
718 		Quit
719 	*/
720 	m_menu.addMenuItem("Brutal Chess", new ChangeMenuItem("New Game", "New Game"));
721 	m_menu.addMenuItem("Brutal Chess", new ChangeMenuItem("Game Options", "Game Options"));
722 	m_menu.addMenuItem("Brutal Chess", new ChangeMenuItem("Graphics", "Graphics"));
723 	m_menu.addMenuItem("Brutal Chess", new ActionItem("Quit", Menu::eQUIT));
724 	m_menu.addMenuItem("Brutal Chess", new SeparatorItem());
725 	m_menu.addMenuItem("Brutal Chess", new ActionItem("Back To Game", Menu::eBACK));
726 
727 	// New Game Menu
728 	m_whiteplayerchoices = new ChoicesItem("White Player");
729 	m_whiteplayerchoices->addChoice(PLAYER_BRUTAL, Menu::eWHITEPLAYERCHANGED);
730 	m_whiteplayerchoices->addChoice(PLAYER_HUMAN, Menu::eWHITEPLAYERCHANGED);
731 	m_whiteplayerchoices->addChoice(PLAYER_RANDOM, Menu::eWHITEPLAYERCHANGED);
732 	m_whiteplayerchoices->setChoice(PLAYER_HUMAN);
733 	m_whitebrutalplychoices = new ChoicesItem("  Difficulty");
734 	m_whitebrutalplychoices->addChoice(AI_DIFFICULTY_EASY, Menu::eWBRUTALPLYCHANGED);
735 	m_whitebrutalplychoices->addChoice(AI_DIFFICULTY_MEDIUM, Menu::eWBRUTALPLYCHANGED);
736 	m_whitebrutalplychoices->addChoice(AI_DIFFICULTY_HARD, Menu::eWBRUTALPLYCHANGED);
737     m_whitebrutalplychoices->setChoice(AI_DIFFICULTY_MEDIUM);
738 	m_blackplayerchoices = new ChoicesItem("Black Player");
739 	m_blackplayerchoices->addChoice(PLAYER_BRUTAL, Menu::eBLACKPLAYERCHANGED);
740 	m_blackplayerchoices->addChoice(PLAYER_HUMAN, Menu::eBLACKPLAYERCHANGED);
741 	m_blackplayerchoices->addChoice(PLAYER_RANDOM, Menu::eBLACKPLAYERCHANGED);
742 	m_blackplayerchoices->setChoice(PLAYER_BRUTAL);
743 	m_blackbrutalplychoices = new ChoicesItem("  Difficulty");
744 	m_blackbrutalplychoices->addChoice(AI_DIFFICULTY_EASY, Menu::eBBRUTALPLYCHANGED);
745 	m_blackbrutalplychoices->addChoice(AI_DIFFICULTY_MEDIUM, Menu::eBBRUTALPLYCHANGED);
746 	m_blackbrutalplychoices->addChoice(AI_DIFFICULTY_HARD, Menu::eBBRUTALPLYCHANGED);
747     m_blackbrutalplychoices->setChoice(AI_DIFFICULTY_MEDIUM);
748 	m_menu.addMenuItem("New Game", m_whiteplayerchoices);
749 	m_menu.addMenuItem("New Game", m_whitebrutalplychoices);
750 	m_menu.addMenuItem("New Game", m_blackplayerchoices);
751 	m_menu.addMenuItem("New Game", m_blackbrutalplychoices);
752 	m_menu.addMenuItem("New Game", new ActionItem("Start New Game", Menu::eSTARTNEWGAME));
753 	m_menu.addMenuItem("New Game", new SeparatorItem());
754 	m_menu.addMenuItem("New Game", new ActionItem("Back", Menu::eBACK));
755 
756 	// Game Options
757     m_menu.addMenuItem("Game Options", new ToggleItem("History Arrows", Menu::eHISTORYARROWSTOG, m_options->historyarrows));
758 	m_menu.addMenuItem("Game Options", new SeparatorItem());
759 	m_menu.addMenuItem("Game Options", new ActionItem("Back", Menu::eBACK));
760 
761 	/* Graphics
762 		Resolution		640x480 800x600 1024x768 1280x1024 1400x1050 1600x1200
763 		Fullscreen
764         Apply resolution
765 		-
766 		Reflections		On Off
767 		Shadows			On Off
768 	*/
769 	m_resolutionchoices = new ChoicesItem("Resolution");
770 	m_resolutionchoices->addChoice("640x480", Menu::e640X480);
771 	m_resolutionchoices->addChoice("800x600", Menu::e800X600);
772 	m_resolutionchoices->addChoice("1024x768", Menu::e1024X768);
773 	m_resolutionchoices->addChoice("1280x1024", Menu::e1280X1024);
774 	m_resolutionchoices->addChoice("1400x1050", Menu::e1400X1050);
775 	m_resolutionchoices->addChoice("1600x1200", Menu::e1600X1200);
776 	m_resolutionchoices->setChoice("800x600");
777 	m_menu.addMenuItem("Graphics", m_resolutionchoices);
778     m_fullscreentoggle = new ToggleItem("Fullscreen", Menu::eFULLSCREENTOG, m_options->fullscreen);
779 	m_menu.addMenuItem("Graphics", m_fullscreentoggle);
780 	m_menu.addMenuItem("Graphics", new ActionItem("Apply resolution", Menu::eAPPLYRES));
781 	m_menu.addMenuItem("Graphics", new SeparatorItem());
782     m_menu.addMenuItem("Graphics", new ToggleItem("Reflections", Menu::eREFLECTTOG, m_options->reflections));
783     m_menu.addMenuItem("Graphics", new ToggleItem("Shadows", Menu::eSHADOWTOG, m_options->shadows));
784 	m_menu.addMenuItem("Graphics", new SeparatorItem());
785 	m_menu.addMenuItem("Graphics", new ActionItem("Back", Menu::eBACK));
786 
787 	m_menu.addMenuItem("Game Over", new ChangeMenuItem("New Game", "New Game"));
788 	m_menu.addMenuItem("Game Over", new ActionItem("Repeat Game", Menu::eSTARTNEWGAME));
789 	m_menu.addMenuItem("Game Over", new SeparatorItem());
790     m_menu.addMenuItem("Game Over", new ActionItem("Main Menu", Menu::eBACK));
791 	m_menu.addMenuItem("Game Over", new ActionItem("Quit", Menu::eQUIT));
792 
793 	m_menu.pushOptionsSet("Brutal Chess");
794 	built = true;
795 	}
796 }
797 
setResolution(std::string resolution)798 void GameCore::setResolution(std::string resolution)
799 {
800 	m_resolutionchoices->setChoice(resolution);
801 }
802 
setFullscreen(bool fullscreen)803 void GameCore::setFullscreen(bool fullscreen)
804 {
805 	m_fullscreentoggle->setOn(fullscreen);
806 }
807 
808 
toBoardSpace()809 void GameCore::toBoardSpace()
810 {
811 	// Setup board space coordinates
812 	glLoadIdentity();
813 	glTranslated(0, 0, -106);
814 	glRotated(42, 1, 0, 0);
815 
816 	glRotated(m_rotatex, 1, 0, 0);
817 	glRotated(m_rotatey, 0, 1, 0);
818 
819 	glScaled(7, 7, 7);
820 	glTranslated(-4.0, 0.0, 4.0);
821 }
updateMouseBoardPos()822 void GameCore::updateMouseBoardPos()
823 {
824 	// Make sure the screen is cleared
825 	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT);
826 
827 	toBoardSpace();
828 
829 	// Draw a large quad to project the mouse position onto
830 	glBegin(GL_QUADS);
831 	glVertex3d(-13, 0,  9.5);
832 	glVertex3d( 20, 0,  9.5);
833 	glVertex3d( 20, 0, -13);
834 	glVertex3d(-13, 0, -13);
835 	glEnd();
836 
837 	GLdouble mvmatrix[16];
838 	GLdouble pjmatrix[16];
839 	GLint viewport[4];
840 
841 	// Get the current window size, modelview and projection matrices
842 	glGetDoublev(GL_MODELVIEW_MATRIX, mvmatrix);
843 	glGetDoublev(GL_PROJECTION_MATRIX, pjmatrix);
844 	glGetIntegerv(GL_VIEWPORT, viewport);
845 
846 	// Measure mouse y coordinate from the bottom of the window
847 	int mousey = viewport[3] - m_mousey;
848 	GLfloat mousedepth;
849 
850 	// Get the depth of the single pixel under the mouse
851 	glReadPixels(m_mousex, mousey, 1, 1, GL_DEPTH_COMPONENT, GL_FLOAT,
852 			&mousedepth);
853 
854 	GLdouble x, y, z;
855 	// Project the mouse position into board coordinates
856 	gluUnProject(m_mousex, mousey, mousedepth, mvmatrix, pjmatrix, viewport,
857 			&x, &y, &z);
858 
859 	m_mouseboardx = x;
860 	m_mouseboardy = z;
861 
862 	m_mousepos = BoardPosition((int)floor(x), -(int)ceil(z));
863 }
864 
callThink(void * pt)865 int callThink(void *pt)
866 {
867 	ChessGame* game = (ChessGame*)pt;
868 	// Give the player time to think
869 	game->getCurrentPlayer()->think(game->getState());
870 
871 	// Finished thinking, let the main thread know
872 	SDL_Event thinkevent;
873 	thinkevent.type = SDL_USEREVENT;
874 	thinkevent.user.code = 0;
875 	thinkevent.user.data1 = game->getCurrentPlayer();
876 	thinkevent.user.data2 = NULL;
877 	SDL_PushEvent(&thinkevent);
878 	return 0;
879 }
880 
spawnThinkThread()881 void GameCore::spawnThinkThread()
882 {
883 	m_thinkthread = SDL_CreateThread(callThink, &m_game);
884 	if(m_thinkthread == NULL) {
885 		cerr << "Unable to create think thread: " << SDL_GetError() << endl;
886 	}
887 
888 	/*
889 	if(!m_game.getCurrentPlayer()->isHuman()) {
890 		SDL_WaitThread(m_thinkthread, NULL);
891 	}
892 	*/
893 }
894 
getPromotionSelection(const BoardPosition & bp)895 Piece::Type GameCore::getPromotionSelection(const BoardPosition & bp)
896 {
897 	if (m_game.getCurrentPlayer()->isWhite()) {
898 		if (bp == BoardPosition('c', 9))
899 			return Piece::QUEEN;
900 		else if (bp == BoardPosition('d', 9))
901 			return Piece::KNIGHT;
902 		else if (bp == BoardPosition('e', 9))
903 			return Piece::BISHOP;
904 		else if (bp == BoardPosition('f', 9))
905 			return Piece::ROOK;
906 	}
907 	else {
908 		if (bp == BoardPosition('c', 0))
909 			return Piece::ROOK;
910 		else if (bp == BoardPosition('d', 0))
911 			return Piece::BISHOP;
912 		else if (bp == BoardPosition('e', 0))
913 			return Piece::KNIGHT;
914 		else if (bp == BoardPosition('f', 0))
915 			return Piece::QUEEN;
916 	}
917 	return Piece::PAWN;
918 }
919 
920 // End of file gamecore.cpp
921