PageRenderTime 48ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/src/modules/ai/astar/8puzzle.cpp

http://ilove2d.googlecode.com/
C++ | 802 lines | 495 code | 199 blank | 108 comment | 56 complexity | 09c14315669c918ea68e216c5eee4e91 MD5 | raw file
  1. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  2. // STL A* Search implementation
  3. // (C)2001 Justin Heyes-Jones
  4. //
  5. // This uses my A* code to solve the 8-puzzle
  6. ////////////////////////////////////////////////////////////////////////////////////////////////////////////////
  7. #include <iostream>
  8. #include <assert.h>
  9. #include <new>
  10. #include <ctype.h>
  11. using namespace std;
  12. // Configuration
  13. #define NUM_TIMES_TO_RUN_SEARCH 1
  14. #define DISPLAY_SOLUTION_FORWARDS 1
  15. #define DISPLAY_SOLUTION_BACKWARDS 0
  16. #define DISPLAY_SOLUTION_INFO 1
  17. #define DEBUG_LISTS 0
  18. // AStar search class
  19. #include "stlastar.h" // See header for copyright and usage information
  20. // Global data
  21. #define BOARD_WIDTH (3)
  22. #define BOARD_HEIGHT (3)
  23. #define GM_TILE (-1)
  24. #define GM_SPACE (0)
  25. #define GM_OFF_BOARD (1)
  26. // Definitions
  27. // To use the search class you must define the following calls...
  28. // Data
  29. // Your own state space information
  30. // Functions
  31. // (Optional) Constructor.
  32. // Nodes are created by the user, so whether you use a
  33. // constructor with parameters as below, or just set the object up after the
  34. // constructor, is up to you.
  35. //
  36. // (Optional) Destructor.
  37. // The destructor will be called if you create one. You
  38. // can rely on the default constructor unless you dynamically allocate something in
  39. // your data
  40. //
  41. // float GoalDistanceEstimate( PuzzleState &nodeGoal );
  42. // Return the estimated cost to goal from this node (pass reference to goal node)
  43. //
  44. // bool IsGoal( PuzzleState &nodeGoal );
  45. // Return true if this node is the goal.
  46. //
  47. // bool GetSuccessors( AStarSearch<PuzzleState> *astarsearch );
  48. // For each successor to this state call the AStarSearch's AddSuccessor call to
  49. // add each one to the current search - return false if you are out of memory and the search
  50. // will fail
  51. //
  52. // float GetCost( PuzzleState *successor );
  53. // Return the cost moving from this state to the state of successor
  54. //
  55. // bool IsSameState( PuzzleState &rhs );
  56. // Return true if the provided state is the same as this state
  57. // Here the example is the 8-puzzle state ...
  58. class PuzzleState
  59. {
  60. public:
  61. // defs
  62. typedef enum
  63. {
  64. TL_SPACE,
  65. TL_1,
  66. TL_2,
  67. TL_3,
  68. TL_4,
  69. TL_5,
  70. TL_6,
  71. TL_7,
  72. TL_8
  73. } TILE;
  74. // data
  75. static TILE g_goal[ BOARD_WIDTH*BOARD_HEIGHT];
  76. static TILE g_start[ BOARD_WIDTH*BOARD_HEIGHT];
  77. // the tile data for the 8-puzzle
  78. TILE tiles[ BOARD_WIDTH*BOARD_HEIGHT ];
  79. // member functions
  80. PuzzleState() {
  81. memcpy( tiles, g_goal, sizeof( TILE ) * BOARD_WIDTH * BOARD_HEIGHT );
  82. }
  83. PuzzleState( TILE *param_tiles )
  84. {
  85. memcpy( tiles, param_tiles, sizeof( TILE ) * BOARD_WIDTH * BOARD_HEIGHT );
  86. }
  87. float GoalDistanceEstimate( PuzzleState &nodeGoal );
  88. bool IsGoal( PuzzleState &nodeGoal );
  89. bool GetSuccessors( AStarSearch<PuzzleState> *astarsearch, PuzzleState *parent_node );
  90. float GetCost( PuzzleState &successor );
  91. bool IsSameState( PuzzleState &rhs );
  92. void PrintNodeInfo();
  93. private:
  94. // User stuff - Just add what you need to help you write the above functions...
  95. void GetSpacePosition( PuzzleState *pn, int *rx, int *ry );
  96. bool LegalMove( TILE *StartTiles, TILE *TargetTiles, int spx, int spy, int tx, int ty );
  97. int GetMap( int x, int y, TILE *tiles );
  98. };
  99. // Goal state
  100. PuzzleState::TILE PuzzleState::g_goal[] =
  101. {
  102. TL_1,
  103. TL_2,
  104. TL_3,
  105. TL_8,
  106. TL_SPACE,
  107. TL_4,
  108. TL_7,
  109. TL_6,
  110. TL_5,
  111. };
  112. // Some nice Start states
  113. PuzzleState::TILE PuzzleState::g_start[] =
  114. {
  115. // Three example start states from Bratko's Prolog Programming for Artificial Intelligence
  116. #if 1
  117. // ex a - 4 steps
  118. TL_1 ,
  119. TL_3 ,
  120. TL_4 ,
  121. TL_8 ,
  122. TL_SPACE ,
  123. TL_2 ,
  124. TL_7 ,
  125. TL_6 ,
  126. TL_5 ,
  127. #elif 0
  128. // ex b - 5 steps
  129. TL_2 ,
  130. TL_8 ,
  131. TL_3 ,
  132. TL_1 ,
  133. TL_6 ,
  134. TL_4 ,
  135. TL_7 ,
  136. TL_SPACE ,
  137. TL_5 ,
  138. #elif 0
  139. // ex c - 18 steps
  140. TL_2 ,
  141. TL_1 ,
  142. TL_6 ,
  143. TL_4 ,
  144. TL_SPACE ,
  145. TL_8 ,
  146. TL_7 ,
  147. TL_5 ,
  148. TL_3 ,
  149. #elif 0
  150. // nasty one - doesn't solve
  151. TL_6 ,
  152. TL_3 ,
  153. TL_SPACE ,
  154. TL_4 ,
  155. TL_8 ,
  156. TL_5 ,
  157. TL_7 ,
  158. TL_2 ,
  159. TL_1 ,
  160. #elif 0
  161. // sent by email - does work though
  162. TL_1 , TL_2 , TL_3 ,
  163. TL_4 , TL_5 , TL_6 ,
  164. TL_8 , TL_7 , TL_SPACE ,
  165. // from http://www.cs.utexas.edu/users/novak/asg-8p.html
  166. //Goal: Easy: Medium: Hard: Worst:
  167. //1 2 3 1 3 4 2 8 1 2 8 1 5 6 7
  168. //8 4 8 6 2 4 3 4 6 3 4 8
  169. //7 6 5 7 5 7 6 5 7 5 3 2 1
  170. #elif 0
  171. // easy 5
  172. TL_1 ,
  173. TL_3 ,
  174. TL_4 ,
  175. TL_8 ,
  176. TL_6 ,
  177. TL_2 ,
  178. TL_7 ,
  179. TL_SPACE ,
  180. TL_5 ,
  181. #elif 0
  182. // medium 9
  183. TL_2 ,
  184. TL_8 ,
  185. TL_1 ,
  186. TL_SPACE ,
  187. TL_4 ,
  188. TL_3 ,
  189. TL_7 ,
  190. TL_6 ,
  191. TL_5 ,
  192. #elif 0
  193. // hard 12
  194. TL_2 ,
  195. TL_8 ,
  196. TL_1 ,
  197. TL_4 ,
  198. TL_6 ,
  199. TL_3 ,
  200. TL_SPACE ,
  201. TL_7 ,
  202. TL_5 ,
  203. #elif 0
  204. // worst 30
  205. TL_5 ,
  206. TL_6 ,
  207. TL_7 ,
  208. TL_4 ,
  209. TL_SPACE ,
  210. TL_8 ,
  211. TL_3 ,
  212. TL_2 ,
  213. TL_1 ,
  214. #elif 0
  215. // 123
  216. // 784
  217. // 65
  218. // two move simple board
  219. TL_1 ,
  220. TL_2 ,
  221. TL_3 ,
  222. TL_7 ,
  223. TL_8 ,
  224. TL_4 ,
  225. TL_SPACE ,
  226. TL_6 ,
  227. TL_5 ,
  228. #elif 0
  229. // a1 b2 c3 d4 e5 f6 g7 h8
  230. //C3,Blank,H8,A1,G8,F6,E5,D4,B2
  231. TL_3 ,
  232. TL_SPACE ,
  233. TL_8 ,
  234. TL_1 ,
  235. TL_8 ,
  236. TL_6 ,
  237. TL_5 ,
  238. TL_4 ,
  239. TL_2 ,
  240. #endif
  241. };
  242. bool PuzzleState::IsSameState( PuzzleState &rhs )
  243. {
  244. for( int i=0; i<(BOARD_HEIGHT*BOARD_WIDTH); i++ )
  245. {
  246. if( tiles[i] != rhs.tiles[i] )
  247. {
  248. return false;
  249. }
  250. }
  251. return true;
  252. }
  253. void PuzzleState::PrintNodeInfo()
  254. {
  255. cout <<
  256. (char) (tiles[0] + '0') <<
  257. (char) (tiles[1] + '0') <<
  258. (char) (tiles[2] + '0') << endl <<
  259. (char) (tiles[3] + '0') <<
  260. (char) (tiles[4] + '0') <<
  261. (char) (tiles[5] + '0') << endl <<
  262. (char) (tiles[6] + '0') <<
  263. (char) (tiles[7] + '0') <<
  264. (char) (tiles[8] + '0') << endl;
  265. }
  266. // Here's the heuristic function that estimates the distance from a PuzzleState
  267. // to the Goal.
  268. float PuzzleState::GoalDistanceEstimate( PuzzleState &nodeGoal )
  269. {
  270. // Nilsson's sequence score
  271. int i, cx, cy, ax, ay, h = 0, s, t;
  272. // given a tile this returns the tile that should be clockwise
  273. TILE correct_follower_to[ BOARD_WIDTH * BOARD_HEIGHT ] =
  274. {
  275. TL_SPACE, // always wrong
  276. TL_2,
  277. TL_3,
  278. TL_4,
  279. TL_5,
  280. TL_6,
  281. TL_7,
  282. TL_8,
  283. TL_1,
  284. };
  285. // given a table index returns the index of the tile that is clockwise to it 3*3 only
  286. int clockwise_tile_of[ BOARD_WIDTH * BOARD_HEIGHT ] =
  287. {
  288. 1,
  289. 2, // 012
  290. 5, // 345
  291. 0, // 678
  292. -1, // never called with center square
  293. 8,
  294. 3,
  295. 6,
  296. 7
  297. };
  298. int tile_x[ BOARD_WIDTH * BOARD_HEIGHT ] =
  299. {
  300. /* TL_SPACE */ 1,
  301. /* TL_1 */ 0,
  302. /* TL_2 */ 1,
  303. /* TL_3 */ 2,
  304. /* TL_4 */ 2,
  305. /* TL_5 */ 2,
  306. /* TL_6 */ 1,
  307. /* TL_7 */ 0,
  308. /* TL_8 */ 0,
  309. };
  310. int tile_y[ BOARD_WIDTH * BOARD_HEIGHT ] =
  311. {
  312. /* TL_SPACE */ 1,
  313. /* TL_1 */ 0,
  314. /* TL_2 */ 0,
  315. /* TL_3 */ 0,
  316. /* TL_4 */ 1,
  317. /* TL_5 */ 2,
  318. /* TL_6 */ 2,
  319. /* TL_7 */ 2,
  320. /* TL_8 */ 1,
  321. };
  322. s=0;
  323. // score 1 point if centre is not correct
  324. if( tiles[(BOARD_HEIGHT*BOARD_WIDTH)/2] != nodeGoal.tiles[(BOARD_HEIGHT*BOARD_WIDTH)/2] )
  325. {
  326. s = 1;
  327. }
  328. for( i=0; i<(BOARD_HEIGHT*BOARD_WIDTH); i++ )
  329. {
  330. // this loop adds up the totaldist element in h and
  331. // the sequence score in s
  332. // the space does not count
  333. if( tiles[i] == TL_SPACE )
  334. {
  335. continue;
  336. }
  337. // get correct x and y of this tile
  338. cx = tile_x[tiles[i]];
  339. cy = tile_y[tiles[i]];
  340. // get actual
  341. ax = i % BOARD_WIDTH;
  342. ay = i / BOARD_WIDTH;
  343. // add manhatten distance to h
  344. h += abs( cx-ax );
  345. h += abs( cy-ay );
  346. // no s score for center tile
  347. if( (ax == (BOARD_WIDTH/2)) && (ay == (BOARD_HEIGHT/2)) )
  348. {
  349. continue;
  350. }
  351. // score 2 points if not followed by successor
  352. if( correct_follower_to[ tiles[i] ] != tiles[ clockwise_tile_of[ i ] ] )
  353. {
  354. s += 2;
  355. }
  356. }
  357. // mult by 3 and add to h
  358. t = h + (3*s);
  359. return (float) t;
  360. }
  361. bool PuzzleState::IsGoal( PuzzleState &nodeGoal )
  362. {
  363. return IsSameState( nodeGoal );
  364. }
  365. // Helper
  366. // Return the x and y position of the space tile
  367. void PuzzleState::GetSpacePosition( PuzzleState *pn, int *rx, int *ry )
  368. {
  369. int x,y;
  370. for( y=0; y<BOARD_HEIGHT; y++ )
  371. {
  372. for( x=0; x<BOARD_WIDTH; x++ )
  373. {
  374. if( pn->tiles[(y*BOARD_WIDTH)+x] == TL_SPACE )
  375. {
  376. *rx = x;
  377. *ry = y;
  378. return;
  379. }
  380. }
  381. }
  382. assert( false && "Something went wrong. There's no space on the board" );
  383. }
  384. int PuzzleState::GetMap( int x, int y, TILE *tiles )
  385. {
  386. if( x < 0 ||
  387. x >= BOARD_WIDTH ||
  388. y < 0 ||
  389. y >= BOARD_HEIGHT
  390. )
  391. return GM_OFF_BOARD;
  392. if( tiles[(y*BOARD_WIDTH)+x] == TL_SPACE )
  393. {
  394. return GM_SPACE;
  395. }
  396. return GM_TILE;
  397. }
  398. // Given a node set of tiles and a set of tiles to move them into, do the move as if it was on a tile board
  399. // note : returns false if the board wasn't changed, and simply returns the tiles as they were in the target
  400. // spx and spy is the space position while tx and ty is the target move from position
  401. bool PuzzleState::LegalMove( TILE *StartTiles, TILE *TargetTiles, int spx, int spy, int tx, int ty )
  402. {
  403. int t;
  404. if( GetMap( spx, spy, StartTiles ) == GM_SPACE )
  405. {
  406. if( GetMap( tx, ty, StartTiles ) == GM_TILE )
  407. {
  408. // copy tiles
  409. for( t=0; t<(BOARD_HEIGHT*BOARD_WIDTH); t++ )
  410. {
  411. TargetTiles[t] = StartTiles[t];
  412. }
  413. TargetTiles[ (ty*BOARD_WIDTH)+tx ] = StartTiles[ (spy*BOARD_WIDTH)+spx ];
  414. TargetTiles[ (spy*BOARD_WIDTH)+spx ] = StartTiles[ (ty*BOARD_WIDTH)+tx ];
  415. return true;
  416. }
  417. }
  418. return false;
  419. }
  420. // This generates the successors to the given PuzzleState. It uses a helper function called
  421. // AddSuccessor to give the successors to the AStar class. The A* specific initialisation
  422. // is done for each node internally, so here you just set the state information that
  423. // is specific to the application
  424. bool PuzzleState::GetSuccessors( AStarSearch<PuzzleState> *astarsearch, PuzzleState *parent_node )
  425. {
  426. PuzzleState NewNode;
  427. int sp_x,sp_y;
  428. GetSpacePosition( this, &sp_x, &sp_y );
  429. bool ret;
  430. if( LegalMove( tiles, NewNode.tiles, sp_x, sp_y, sp_x, sp_y-1 ) == true )
  431. {
  432. ret = astarsearch->AddSuccessor( NewNode );
  433. if( !ret ) return false;
  434. }
  435. if( LegalMove( tiles, NewNode.tiles, sp_x, sp_y, sp_x, sp_y+1 ) == true )
  436. {
  437. ret = astarsearch->AddSuccessor( NewNode );
  438. if( !ret ) return false;
  439. }
  440. if( LegalMove( tiles, NewNode.tiles, sp_x, sp_y, sp_x-1, sp_y ) == true )
  441. {
  442. ret = astarsearch->AddSuccessor( NewNode );
  443. if( !ret ) return false;
  444. }
  445. if( LegalMove( tiles, NewNode.tiles, sp_x, sp_y, sp_x+1, sp_y ) == true )
  446. {
  447. ret = astarsearch->AddSuccessor( NewNode );
  448. if( !ret ) return false;
  449. }
  450. return true;
  451. }
  452. // given this node, what does it cost to move to successor. In the case
  453. // of our map the answer is the map terrain value at this node since that is
  454. // conceptually where we're moving
  455. float PuzzleState::GetCost( PuzzleState &successor )
  456. {
  457. return 1.0f; // I love it when life is simple
  458. }
  459. // Main
  460. int main( int argc, char *argv[] )
  461. {
  462. cout << "STL A* 8-puzzle solver implementation\n(C)2001 Justin Heyes-Jones\n";
  463. bool bUserBoard = false;
  464. if( argc > 1 )
  465. {
  466. char *userboard = argv[1];
  467. int i = 0;
  468. int c;
  469. while( c = argv[1][i] )
  470. {
  471. if( isdigit( c ) )
  472. {
  473. int num = (c - '0');
  474. PuzzleState::g_start[i] = static_cast<PuzzleState::TILE>(num);
  475. }
  476. i++;
  477. }
  478. }
  479. // Create an instance of the search class...
  480. AStarSearch<PuzzleState> astarsearch;
  481. int NumTimesToSearch = NUM_TIMES_TO_RUN_SEARCH;
  482. while( NumTimesToSearch-- )
  483. {
  484. // Create a start state
  485. PuzzleState nodeStart( PuzzleState::g_start );
  486. // Define the goal state
  487. PuzzleState nodeEnd( PuzzleState::g_goal );
  488. // Set Start and goal states
  489. astarsearch.SetStartAndGoalStates( nodeStart, nodeEnd );
  490. unsigned int SearchState;
  491. unsigned int SearchSteps = 0;
  492. do
  493. {
  494. SearchState = astarsearch.SearchStep();
  495. #if DEBUG_LISTS
  496. float f,g,h;
  497. cout << "Search step " << SearchSteps << endl;
  498. cout << "Open:\n";
  499. PuzzleState *p = astarsearch.GetOpenListStart( f,g,h );
  500. while( p )
  501. {
  502. ((PuzzleState *)p)->PrintNodeInfo();
  503. cout << "f: " << f << " g: " << g << " h: " << h << "\n\n";
  504. p = astarsearch.GetOpenListNext( f,g,h );
  505. }
  506. cout << "Closed:\n";
  507. p = astarsearch.GetClosedListStart( f,g,h );
  508. while( p )
  509. {
  510. p->PrintNodeInfo();
  511. cout << "f: " << f << " g: " << g << " h: " << h << "\n\n";
  512. p = astarsearch.GetClosedListNext( f,g,h );
  513. }
  514. #endif
  515. // Test cancel search
  516. #if 0
  517. int StepCount = astarsearch.GetStepCount();
  518. if( StepCount == 10 )
  519. {
  520. astarsearch.CancelSearch();
  521. }
  522. #endif
  523. SearchSteps++;
  524. }
  525. while( SearchState == AStarSearch<PuzzleState>::SEARCH_STATE_SEARCHING );
  526. if( SearchState == AStarSearch<PuzzleState>::SEARCH_STATE_SUCCEEDED )
  527. {
  528. #if DISPLAY_SOLUTION_FORWARDS
  529. cout << "Search found goal state\n";
  530. #endif
  531. PuzzleState *node = astarsearch.GetSolutionStart();
  532. #if DISPLAY_SOLUTION_FORWARDS
  533. cout << "Displaying solution\n";
  534. #endif
  535. int steps = 0;
  536. #if DISPLAY_SOLUTION_FORWARDS
  537. node->PrintNodeInfo();
  538. cout << endl;
  539. #endif
  540. for( ;; )
  541. {
  542. node = astarsearch.GetSolutionNext();
  543. if( !node )
  544. {
  545. break;
  546. }
  547. #if DISPLAY_SOLUTION_FORWARDS
  548. node->PrintNodeInfo();
  549. cout << endl;
  550. #endif
  551. steps ++;
  552. };
  553. #if DISPLAY_SOLUTION_FORWARDS
  554. // todo move step count into main algorithm
  555. cout << "Solution steps " << steps << endl;
  556. #endif
  557. ////////////
  558. node = astarsearch.GetSolutionEnd();
  559. #if DISPLAY_SOLUTION_BACKWARDS
  560. cout << "Displaying reverse solution\n";
  561. #endif
  562. steps = 0;
  563. node->PrintNodeInfo();
  564. cout << endl;
  565. for( ;; )
  566. {
  567. node = astarsearch.GetSolutionPrev();
  568. if( !node )
  569. {
  570. break;
  571. }
  572. #if DISPLAY_SOLUTION_BACKWARDS
  573. node->PrintNodeInfo();
  574. cout << endl;
  575. #endif
  576. steps ++;
  577. };
  578. #if DISPLAY_SOLUTION_BACKWARDS
  579. cout << "Solution steps " << steps << endl;
  580. #endif
  581. //////////////
  582. // Once you're done with the solution you can free the nodes up
  583. astarsearch.FreeSolutionNodes();
  584. }
  585. else if( SearchState == AStarSearch<PuzzleState>::SEARCH_STATE_FAILED )
  586. {
  587. #if DISPLAY_SOLUTION_INFO
  588. cout << "Search terminated. Did not find goal state\n";
  589. #endif
  590. }
  591. else if( SearchState == AStarSearch<PuzzleState>::SEARCH_STATE_OUT_OF_MEMORY )
  592. {
  593. #if DISPLAY_SOLUTION_INFO
  594. cout << "Search terminated. Out of memory\n";
  595. #endif
  596. }
  597. // Display the number of loops the search went through
  598. #if DISPLAY_SOLUTION_INFO
  599. cout << "SearchSteps : " << astarsearch.GetStepCount() << endl;
  600. #endif
  601. }
  602. return 0;
  603. }