From 68a400df8ac7cb1ea6e82949c6a69999e2a70ea2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BE=90=E6=B6=9B?= <2064227687@qq.com> Date: Mon, 11 Oct 2021 10:39:27 +0800 Subject: [PATCH] search --- proj1-search-python3/VERSION | 1 + .../__pycache__/game.cpython-36.pyc | Bin 0 -> 20955 bytes .../__pycache__/game.cpython-39.pyc | Bin 0 -> 20959 bytes .../__pycache__/ghostAgents.cpython-39.pyc | Bin 0 -> 3275 bytes .../__pycache__/grading.cpython-36.pyc | Bin 0 -> 9402 bytes .../__pycache__/grading.cpython-39.pyc | Bin 0 -> 9478 bytes .../graphicsDisplay.cpython-39.pyc | Bin 0 -> 20410 bytes .../__pycache__/graphicsUtils.cpython-39.pyc | Bin 0 -> 10460 bytes .../__pycache__/keyboardAgents.cpython-39.pyc | Bin 0 -> 2176 bytes .../__pycache__/layout.cpython-36.pyc | Bin 0 -> 5606 bytes .../__pycache__/layout.cpython-39.pyc | Bin 0 -> 5601 bytes .../__pycache__/pacman.cpython-36.pyc | Bin 0 -> 22826 bytes .../__pycache__/pacman.cpython-39.pyc | Bin 0 -> 22913 bytes .../__pycache__/pacmanAgents.cpython-39.pyc | Bin 0 -> 2155 bytes .../__pycache__/projectParams.cpython-36.pyc | Bin 0 -> 340 bytes .../__pycache__/projectParams.cpython-39.pyc | Bin 0 -> 348 bytes .../__pycache__/search.cpython-36.pyc | Bin 0 -> 5524 bytes .../__pycache__/search.cpython-39.pyc | Bin 0 -> 5516 bytes .../__pycache__/searchAgents.cpython-36.pyc | Bin 0 -> 21309 bytes .../__pycache__/searchAgents.cpython-39.pyc | Bin 0 -> 21087 bytes .../searchTestClasses.cpython-36.pyc | Bin 0 -> 24942 bytes .../searchTestClasses.cpython-39.pyc | Bin 0 -> 23943 bytes .../__pycache__/testClasses.cpython-36.pyc | Bin 0 -> 6649 bytes .../__pycache__/testClasses.cpython-39.pyc | Bin 0 -> 6397 bytes .../__pycache__/testParser.cpython-36.pyc | Bin 0 -> 2010 bytes .../__pycache__/testParser.cpython-39.pyc | Bin 0 -> 2035 bytes .../__pycache__/textDisplay.cpython-36.pyc | Bin 0 -> 2946 bytes .../__pycache__/textDisplay.cpython-39.pyc | Bin 0 -> 2984 bytes .../__pycache__/util.cpython-36.pyc | Bin 0 -> 26850 bytes .../__pycache__/util.cpython-39.pyc | Bin 0 -> 23568 bytes proj1-search-python3/autograder.py | 370 ++++++++ proj1-search-python3/commands.txt | 21 + proj1-search-python3/eightpuzzle.py | 281 ++++++ proj1-search-python3/game.py | 729 ++++++++++++++++ proj1-search-python3/ghostAgents.py | 81 ++ proj1-search-python3/grading.py | 329 +++++++ proj1-search-python3/graphicsDisplay.py | 679 +++++++++++++++ proj1-search-python3/graphicsUtils.py | 402 +++++++++ proj1-search-python3/keyboardAgents.py | 84 ++ proj1-search-python3/layout.py | 150 ++++ proj1-search-python3/layouts/bigCorners.lay | 37 + proj1-search-python3/layouts/bigMaze.lay | 37 + .../layouts/bigSafeSearch.lay | 8 + proj1-search-python3/layouts/bigSearch.lay | 15 + proj1-search-python3/layouts/boxSearch.lay | 14 + .../layouts/capsuleClassic.lay | 7 + .../layouts/contestClassic.lay | 9 + proj1-search-python3/layouts/contoursMaze.lay | 11 + proj1-search-python3/layouts/greedySearch.lay | 8 + .../layouts/mediumClassic.lay | 11 + .../layouts/mediumCorners.lay | 14 + .../layouts/mediumDottedMaze.lay | 18 + proj1-search-python3/layouts/mediumMaze.lay | 18 + .../layouts/mediumSafeSearch.lay | 6 + .../layouts/mediumScaryMaze.lay | 18 + proj1-search-python3/layouts/mediumSearch.lay | 8 + .../layouts/minimaxClassic.lay | 5 + proj1-search-python3/layouts/oddSearch.lay | 7 + proj1-search-python3/layouts/openClassic.lay | 9 + proj1-search-python3/layouts/openMaze.lay | 23 + proj1-search-python3/layouts/openSearch.lay | 7 + .../layouts/originalClassic.lay | 27 + proj1-search-python3/layouts/powerClassic.lay | 7 + proj1-search-python3/layouts/smallClassic.lay | 7 + proj1-search-python3/layouts/smallMaze.lay | 10 + .../layouts/smallSafeSearch.lay | 15 + proj1-search-python3/layouts/smallSearch.lay | 5 + proj1-search-python3/layouts/testClassic.lay | 10 + proj1-search-python3/layouts/testMaze.lay | 3 + proj1-search-python3/layouts/testSearch.lay | 5 + proj1-search-python3/layouts/tinyCorners.lay | 8 + proj1-search-python3/layouts/tinyMaze.lay | 7 + .../layouts/tinySafeSearch.lay | 7 + proj1-search-python3/layouts/tinySearch.lay | 7 + .../layouts/trappedClassic.lay | 5 + .../layouts/trickyClassic.lay | 13 + proj1-search-python3/layouts/trickySearch.lay | 7 + proj1-search-python3/pacman.py | 684 +++++++++++++++ proj1-search-python3/pacmanAgents.py | 52 ++ proj1-search-python3/projectParams.py | 18 + proj1-search-python3/search.py | 205 +++++ proj1-search-python3/searchAgents.py | 575 ++++++++++++ proj1-search-python3/searchTestClasses.py | 823 ++++++++++++++++++ proj1-search-python3/testClasses.py | 206 +++++ proj1-search-python3/testParser.py | 85 ++ proj1-search-python3/test_cases/CONFIG | 1 + proj1-search-python3/test_cases/q1/CONFIG | 2 + .../test_cases/q1/graph_backtrack.solution | 7 + .../test_cases/q1/graph_backtrack.test | 32 + .../test_cases/q1/graph_bfs_vs_dfs.solution | 7 + .../test_cases/q1/graph_bfs_vs_dfs.test | 30 + .../test_cases/q1/graph_infinite.solution | 7 + .../test_cases/q1/graph_infinite.test | 30 + .../test_cases/q1/graph_manypaths.solution | 7 + .../test_cases/q1/graph_manypaths.test | 39 + .../test_cases/q1/pacman_1.solution | 40 + .../test_cases/q1/pacman_1.test | 27 + proj1-search-python3/test_cases/q2/CONFIG | 2 + .../test_cases/q2/graph_backtrack.solution | 7 + .../test_cases/q2/graph_backtrack.test | 32 + .../test_cases/q2/graph_bfs_vs_dfs.solution | 7 + .../test_cases/q2/graph_bfs_vs_dfs.test | 30 + .../test_cases/q2/graph_infinite.solution | 7 + .../test_cases/q2/graph_infinite.test | 30 + .../test_cases/q2/graph_manypaths.solution | 7 + .../test_cases/q2/graph_manypaths.test | 39 + .../test_cases/q2/pacman_1.solution | 22 + .../test_cases/q2/pacman_1.test | 27 + proj1-search-python3/test_cases/q3/CONFIG | 2 + .../test_cases/q3/graph_backtrack.solution | 7 + .../test_cases/q3/graph_backtrack.test | 32 + .../test_cases/q3/graph_bfs_vs_dfs.solution | 7 + .../test_cases/q3/graph_bfs_vs_dfs.test | 30 + .../test_cases/q3/graph_infinite.solution | 7 + .../test_cases/q3/graph_infinite.test | 30 + .../test_cases/q3/graph_manypaths.solution | 7 + .../test_cases/q3/graph_manypaths.test | 39 + .../test_cases/q3/ucs_0_graph.solution | 7 + .../test_cases/q3/ucs_0_graph.test | 39 + .../test_cases/q3/ucs_1_problemC.solution | 22 + .../test_cases/q3/ucs_1_problemC.test | 28 + .../test_cases/q3/ucs_2_problemE.solution | 22 + .../test_cases/q3/ucs_2_problemE.test | 28 + .../test_cases/q3/ucs_3_problemW.solution | 34 + .../test_cases/q3/ucs_3_problemW.test | 28 + .../test_cases/q3/ucs_4_testSearch.solution | 12 + .../test_cases/q3/ucs_4_testSearch.test | 16 + .../q3/ucs_5_goalAtDequeue.solution | 7 + .../test_cases/q3/ucs_5_goalAtDequeue.test | 29 + proj1-search-python3/test_cases/q4/CONFIG | 2 + .../test_cases/q4/astar_0.solution | 7 + .../test_cases/q4/astar_0.test | 39 + .../q4/astar_1_graph_heuristic.solution | 7 + .../q4/astar_1_graph_heuristic.test | 54 ++ .../test_cases/q4/astar_2_manhattan.solution | 22 + .../test_cases/q4/astar_2_manhattan.test | 27 + .../q4/astar_3_goalAtDequeue.solution | 7 + .../test_cases/q4/astar_3_goalAtDequeue.test | 29 + .../test_cases/q4/graph_backtrack.solution | 7 + .../test_cases/q4/graph_backtrack.test | 32 + .../test_cases/q4/graph_manypaths.solution | 7 + .../test_cases/q4/graph_manypaths.test | 39 + proj1-search-python3/test_cases/q5/CONFIG | 3 + .../test_cases/q5/corner_tiny_corner.solution | 2 + .../test_cases/q5/corner_tiny_corner.test | 14 + proj1-search-python3/test_cases/q6/CONFIG | 3 + .../test_cases/q6/corner_sanity_1.solution | 7 + .../test_cases/q6/corner_sanity_1.test | 12 + .../test_cases/q6/corner_sanity_2.solution | 7 + .../test_cases/q6/corner_sanity_2.test | 12 + .../test_cases/q6/corner_sanity_3.solution | 9 + .../test_cases/q6/corner_sanity_3.test | 15 + .../test_cases/q6/medium_corners.solution | 16 + .../test_cases/q6/medium_corners.test | 19 + proj1-search-python3/test_cases/q7/CONFIG | 3 + .../test_cases/q7/food_heuristic_1.solution | 2 + .../test_cases/q7/food_heuristic_1.test | 13 + .../test_cases/q7/food_heuristic_10.solution | 2 + .../test_cases/q7/food_heuristic_10.test | 13 + .../test_cases/q7/food_heuristic_11.solution | 2 + .../test_cases/q7/food_heuristic_11.test | 13 + .../test_cases/q7/food_heuristic_12.solution | 2 + .../test_cases/q7/food_heuristic_12.test | 13 + .../test_cases/q7/food_heuristic_13.solution | 2 + .../test_cases/q7/food_heuristic_13.test | 13 + .../test_cases/q7/food_heuristic_14.solution | 2 + .../test_cases/q7/food_heuristic_14.test | 19 + .../test_cases/q7/food_heuristic_15.solution | 2 + .../test_cases/q7/food_heuristic_15.test | 32 + .../test_cases/q7/food_heuristic_16.solution | 2 + .../test_cases/q7/food_heuristic_16.test | 15 + .../test_cases/q7/food_heuristic_17.solution | 2 + .../test_cases/q7/food_heuristic_17.test | 14 + .../test_cases/q7/food_heuristic_2.solution | 2 + .../test_cases/q7/food_heuristic_2.test | 32 + .../test_cases/q7/food_heuristic_3.solution | 2 + .../test_cases/q7/food_heuristic_3.test | 15 + .../test_cases/q7/food_heuristic_4.solution | 2 + .../test_cases/q7/food_heuristic_4.test | 14 + .../test_cases/q7/food_heuristic_5.solution | 2 + .../test_cases/q7/food_heuristic_5.test | 13 + .../test_cases/q7/food_heuristic_6.solution | 2 + .../test_cases/q7/food_heuristic_6.test | 13 + .../test_cases/q7/food_heuristic_7.solution | 2 + .../test_cases/q7/food_heuristic_7.test | 13 + .../test_cases/q7/food_heuristic_8.solution | 2 + .../test_cases/q7/food_heuristic_8.test | 13 + .../test_cases/q7/food_heuristic_9.solution | 2 + .../test_cases/q7/food_heuristic_9.test | 13 + .../q7/food_heuristic_grade_tricky.solution | 2 + .../q7/food_heuristic_grade_tricky.test | 19 + proj1-search-python3/test_cases/q8/CONFIG | 2 + .../test_cases/q8/closest_dot_1.solution | 2 + .../test_cases/q8/closest_dot_1.test | 11 + .../test_cases/q8/closest_dot_10.solution | 2 + .../test_cases/q8/closest_dot_10.test | 17 + .../test_cases/q8/closest_dot_11.solution | 2 + .../test_cases/q8/closest_dot_11.test | 30 + .../test_cases/q8/closest_dot_12.solution | 2 + .../test_cases/q8/closest_dot_12.test | 13 + .../test_cases/q8/closest_dot_13.solution | 2 + .../test_cases/q8/closest_dot_13.test | 12 + .../test_cases/q8/closest_dot_2.solution | 2 + .../test_cases/q8/closest_dot_2.test | 11 + .../test_cases/q8/closest_dot_3.solution | 2 + .../test_cases/q8/closest_dot_3.test | 11 + .../test_cases/q8/closest_dot_4.solution | 2 + .../test_cases/q8/closest_dot_4.test | 11 + .../test_cases/q8/closest_dot_5.solution | 2 + .../test_cases/q8/closest_dot_5.test | 11 + .../test_cases/q8/closest_dot_6.solution | 2 + .../test_cases/q8/closest_dot_6.test | 11 + .../test_cases/q8/closest_dot_7.solution | 2 + .../test_cases/q8/closest_dot_7.test | 11 + .../test_cases/q8/closest_dot_8.solution | 2 + .../test_cases/q8/closest_dot_8.test | 11 + .../test_cases/q8/closest_dot_9.solution | 2 + .../test_cases/q8/closest_dot_9.test | 11 + proj1-search-python3/textDisplay.py | 81 ++ proj1-search-python3/util.py | 672 ++++++++++++++ search/VERSION | 1 + search/autograder.py | 358 ++++++++ search/commands.txt | 22 + search/eightpuzzle.py | 281 ++++++ search/game.py | 729 ++++++++++++++++ search/game.pyc | Bin 0 -> 24953 bytes search/ghostAgents.py | 81 ++ search/ghostAgents.pyc | Bin 0 -> 3611 bytes search/grading.py | 323 +++++++ search/grading.pyc | Bin 0 -> 10991 bytes search/graphicsDisplay.py | 679 +++++++++++++++ search/graphicsDisplay.pyc | Bin 0 -> 26138 bytes search/graphicsUtils.py | 402 +++++++++ search/graphicsUtils.pyc | Bin 0 -> 14217 bytes search/keyboardAgents.py | 84 ++ search/keyboardAgents.pyc | Bin 0 -> 2973 bytes search/layout.py | 149 ++++ search/layout.pyc | Bin 0 -> 6701 bytes search/layouts/bigCorners.lay | 37 + search/layouts/bigMaze.lay | 37 + search/layouts/bigSafeSearch.lay | 8 + search/layouts/bigSearch.lay | 15 + search/layouts/boxSearch.lay | 14 + search/layouts/capsuleClassic.lay | 7 + search/layouts/contestClassic.lay | 9 + search/layouts/contoursMaze.lay | 11 + search/layouts/greedySearch.lay | 8 + search/layouts/mediumClassic.lay | 11 + search/layouts/mediumCorners.lay | 14 + search/layouts/mediumDottedMaze.lay | 18 + search/layouts/mediumMaze.lay | 18 + search/layouts/mediumSafeSearch.lay | 6 + search/layouts/mediumScaryMaze.lay | 18 + search/layouts/mediumSearch.lay | 8 + search/layouts/minimaxClassic.lay | 5 + search/layouts/oddSearch.lay | 7 + search/layouts/openClassic.lay | 9 + search/layouts/openMaze.lay | 23 + search/layouts/openSearch.lay | 7 + search/layouts/originalClassic.lay | 27 + search/layouts/powerClassic.lay | 7 + search/layouts/smallClassic.lay | 7 + search/layouts/smallMaze.lay | 10 + search/layouts/smallSafeSearch.lay | 15 + search/layouts/smallSearch.lay | 5 + search/layouts/testClassic.lay | 10 + search/layouts/testMaze.lay | 3 + search/layouts/testSearch.lay | 5 + search/layouts/tinyCorners.lay | 8 + search/layouts/tinyMaze.lay | 7 + search/layouts/tinySafeSearch.lay | 7 + search/layouts/tinySearch.lay | 7 + search/layouts/trappedClassic.lay | 5 + search/layouts/trickyClassic.lay | 13 + search/layouts/trickySearch.lay | 7 + search/pacman.py | 684 +++++++++++++++ search/pacman.pyc | Bin 0 -> 27197 bytes search/pacmanAgents.py | 52 ++ search/pacmanAgents.pyc | Bin 0 -> 2414 bytes search/projectParams.py | 18 + search/projectParams.pyc | Bin 0 -> 356 bytes search/search.py | 211 +++++ search/search.pyc | Bin 0 -> 6809 bytes search/searchAgents.py | 576 ++++++++++++ search/searchAgents.pyc | Bin 0 -> 22415 bytes search/searchTestClasses.py | 821 +++++++++++++++++ search/searchTestClasses.pyc | Bin 0 -> 26345 bytes search/submission_autograder.py | 41 + search/testClasses.py | 206 +++++ search/testClasses.pyc | Bin 0 -> 8736 bytes search/testParser.py | 85 ++ search/testParser.pyc | Bin 0 -> 2609 bytes search/test_cases/CONFIG | 1 + search/test_cases/q1/CONFIG | 2 + search/test_cases/q1/graph_backtrack.solution | 7 + search/test_cases/q1/graph_backtrack.test | 32 + .../test_cases/q1/graph_bfs_vs_dfs.solution | 7 + search/test_cases/q1/graph_bfs_vs_dfs.test | 30 + search/test_cases/q1/graph_infinite.solution | 7 + search/test_cases/q1/graph_infinite.test | 30 + search/test_cases/q1/graph_manypaths.solution | 7 + search/test_cases/q1/graph_manypaths.test | 39 + search/test_cases/q1/pacman_1.solution | 40 + search/test_cases/q1/pacman_1.test | 27 + search/test_cases/q2/CONFIG | 2 + search/test_cases/q2/graph_backtrack.solution | 7 + search/test_cases/q2/graph_backtrack.test | 32 + .../test_cases/q2/graph_bfs_vs_dfs.solution | 7 + search/test_cases/q2/graph_bfs_vs_dfs.test | 30 + search/test_cases/q2/graph_infinite.solution | 7 + search/test_cases/q2/graph_infinite.test | 30 + search/test_cases/q2/graph_manypaths.solution | 7 + search/test_cases/q2/graph_manypaths.test | 39 + search/test_cases/q2/pacman_1.solution | 22 + search/test_cases/q2/pacman_1.test | 27 + search/test_cases/q3/CONFIG | 2 + search/test_cases/q3/graph_backtrack.solution | 7 + search/test_cases/q3/graph_backtrack.test | 32 + .../test_cases/q3/graph_bfs_vs_dfs.solution | 7 + search/test_cases/q3/graph_bfs_vs_dfs.test | 30 + search/test_cases/q3/graph_infinite.solution | 7 + search/test_cases/q3/graph_infinite.test | 30 + search/test_cases/q3/graph_manypaths.solution | 7 + search/test_cases/q3/graph_manypaths.test | 39 + search/test_cases/q3/ucs_0_graph.solution | 7 + search/test_cases/q3/ucs_0_graph.test | 39 + search/test_cases/q3/ucs_1_problemC.solution | 22 + search/test_cases/q3/ucs_1_problemC.test | 28 + search/test_cases/q3/ucs_2_problemE.solution | 22 + search/test_cases/q3/ucs_2_problemE.test | 28 + search/test_cases/q3/ucs_3_problemW.solution | 34 + search/test_cases/q3/ucs_3_problemW.test | 28 + .../test_cases/q3/ucs_4_testSearch.solution | 12 + search/test_cases/q3/ucs_4_testSearch.test | 16 + .../q3/ucs_5_goalAtDequeue.solution | 7 + search/test_cases/q3/ucs_5_goalAtDequeue.test | 29 + search/test_cases/q4/CONFIG | 2 + search/test_cases/q4/astar_0.solution | 7 + search/test_cases/q4/astar_0.test | 39 + .../q4/astar_1_graph_heuristic.solution | 7 + .../q4/astar_1_graph_heuristic.test | 54 ++ .../test_cases/q4/astar_2_manhattan.solution | 22 + search/test_cases/q4/astar_2_manhattan.test | 27 + .../q4/astar_3_goalAtDequeue.solution | 7 + .../test_cases/q4/astar_3_goalAtDequeue.test | 29 + search/test_cases/q4/graph_backtrack.solution | 7 + search/test_cases/q4/graph_backtrack.test | 32 + search/test_cases/q4/graph_manypaths.solution | 7 + search/test_cases/q4/graph_manypaths.test | 39 + search/test_cases/q5/CONFIG | 3 + .../test_cases/q5/corner_tiny_corner.solution | 2 + search/test_cases/q5/corner_tiny_corner.test | 14 + search/test_cases/q6/CONFIG | 3 + search/test_cases/q6/corner_sanity_1.solution | 7 + search/test_cases/q6/corner_sanity_1.test | 12 + search/test_cases/q6/corner_sanity_2.solution | 7 + search/test_cases/q6/corner_sanity_2.test | 12 + search/test_cases/q6/corner_sanity_3.solution | 9 + search/test_cases/q6/corner_sanity_3.test | 15 + search/test_cases/q6/medium_corners.solution | 16 + search/test_cases/q6/medium_corners.test | 19 + search/test_cases/q7/CONFIG | 3 + .../test_cases/q7/food_heuristic_1.solution | 2 + search/test_cases/q7/food_heuristic_1.test | 13 + .../test_cases/q7/food_heuristic_10.solution | 2 + search/test_cases/q7/food_heuristic_10.test | 13 + .../test_cases/q7/food_heuristic_11.solution | 2 + search/test_cases/q7/food_heuristic_11.test | 13 + .../test_cases/q7/food_heuristic_12.solution | 2 + search/test_cases/q7/food_heuristic_12.test | 13 + .../test_cases/q7/food_heuristic_13.solution | 2 + search/test_cases/q7/food_heuristic_13.test | 13 + .../test_cases/q7/food_heuristic_14.solution | 2 + search/test_cases/q7/food_heuristic_14.test | 19 + .../test_cases/q7/food_heuristic_15.solution | 2 + search/test_cases/q7/food_heuristic_15.test | 32 + .../test_cases/q7/food_heuristic_16.solution | 2 + search/test_cases/q7/food_heuristic_16.test | 15 + .../test_cases/q7/food_heuristic_17.solution | 2 + search/test_cases/q7/food_heuristic_17.test | 14 + .../test_cases/q7/food_heuristic_2.solution | 2 + search/test_cases/q7/food_heuristic_2.test | 32 + .../test_cases/q7/food_heuristic_3.solution | 2 + search/test_cases/q7/food_heuristic_3.test | 15 + .../test_cases/q7/food_heuristic_4.solution | 2 + search/test_cases/q7/food_heuristic_4.test | 14 + .../test_cases/q7/food_heuristic_5.solution | 2 + search/test_cases/q7/food_heuristic_5.test | 13 + .../test_cases/q7/food_heuristic_6.solution | 2 + search/test_cases/q7/food_heuristic_6.test | 13 + .../test_cases/q7/food_heuristic_7.solution | 2 + search/test_cases/q7/food_heuristic_7.test | 13 + .../test_cases/q7/food_heuristic_8.solution | 2 + search/test_cases/q7/food_heuristic_8.test | 13 + .../test_cases/q7/food_heuristic_9.solution | 2 + search/test_cases/q7/food_heuristic_9.test | 13 + .../q7/food_heuristic_grade_tricky.solution | 2 + .../q7/food_heuristic_grade_tricky.test | 19 + search/test_cases/q8/CONFIG | 2 + search/test_cases/q8/closest_dot_1.solution | 2 + search/test_cases/q8/closest_dot_1.test | 11 + search/test_cases/q8/closest_dot_10.solution | 2 + search/test_cases/q8/closest_dot_10.test | 17 + search/test_cases/q8/closest_dot_11.solution | 2 + search/test_cases/q8/closest_dot_11.test | 30 + search/test_cases/q8/closest_dot_12.solution | 2 + search/test_cases/q8/closest_dot_12.test | 13 + search/test_cases/q8/closest_dot_13.solution | 2 + search/test_cases/q8/closest_dot_13.test | 12 + search/test_cases/q8/closest_dot_2.solution | 2 + search/test_cases/q8/closest_dot_2.test | 11 + search/test_cases/q8/closest_dot_3.solution | 2 + search/test_cases/q8/closest_dot_3.test | 11 + search/test_cases/q8/closest_dot_4.solution | 2 + search/test_cases/q8/closest_dot_4.test | 11 + search/test_cases/q8/closest_dot_5.solution | 2 + search/test_cases/q8/closest_dot_5.test | 11 + search/test_cases/q8/closest_dot_6.solution | 2 + search/test_cases/q8/closest_dot_6.test | 11 + search/test_cases/q8/closest_dot_7.solution | 2 + search/test_cases/q8/closest_dot_7.test | 11 + search/test_cases/q8/closest_dot_8.solution | 2 + search/test_cases/q8/closest_dot_8.test | 11 + search/test_cases/q8/closest_dot_9.solution | 2 + search/test_cases/q8/closest_dot_9.test | 11 + search/textDisplay.py | 81 ++ search/textDisplay.pyc | Bin 0 -> 3800 bytes search/util.py | 674 ++++++++++++++ search/util.pyc | Bin 0 -> 35116 bytes 429 files changed, 17548 insertions(+) create mode 100644 proj1-search-python3/VERSION create mode 100644 proj1-search-python3/__pycache__/game.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/game.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/ghostAgents.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/grading.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/grading.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/graphicsDisplay.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/graphicsUtils.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/keyboardAgents.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/layout.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/layout.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/pacman.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/pacman.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/pacmanAgents.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/projectParams.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/projectParams.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/search.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/search.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/searchAgents.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/searchAgents.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/searchTestClasses.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/searchTestClasses.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/testClasses.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/testClasses.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/testParser.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/testParser.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/textDisplay.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/textDisplay.cpython-39.pyc create mode 100644 proj1-search-python3/__pycache__/util.cpython-36.pyc create mode 100644 proj1-search-python3/__pycache__/util.cpython-39.pyc create mode 100644 proj1-search-python3/autograder.py create mode 100644 proj1-search-python3/commands.txt create mode 100644 proj1-search-python3/eightpuzzle.py create mode 100644 proj1-search-python3/game.py create mode 100644 proj1-search-python3/ghostAgents.py create mode 100644 proj1-search-python3/grading.py create mode 100644 proj1-search-python3/graphicsDisplay.py create mode 100644 proj1-search-python3/graphicsUtils.py create mode 100644 proj1-search-python3/keyboardAgents.py create mode 100644 proj1-search-python3/layout.py create mode 100644 proj1-search-python3/layouts/bigCorners.lay create mode 100644 proj1-search-python3/layouts/bigMaze.lay create mode 100644 proj1-search-python3/layouts/bigSafeSearch.lay create mode 100644 proj1-search-python3/layouts/bigSearch.lay create mode 100644 proj1-search-python3/layouts/boxSearch.lay create mode 100644 proj1-search-python3/layouts/capsuleClassic.lay create mode 100644 proj1-search-python3/layouts/contestClassic.lay create mode 100644 proj1-search-python3/layouts/contoursMaze.lay create mode 100644 proj1-search-python3/layouts/greedySearch.lay create mode 100644 proj1-search-python3/layouts/mediumClassic.lay create mode 100644 proj1-search-python3/layouts/mediumCorners.lay create mode 100644 proj1-search-python3/layouts/mediumDottedMaze.lay create mode 100644 proj1-search-python3/layouts/mediumMaze.lay create mode 100644 proj1-search-python3/layouts/mediumSafeSearch.lay create mode 100644 proj1-search-python3/layouts/mediumScaryMaze.lay create mode 100644 proj1-search-python3/layouts/mediumSearch.lay create mode 100644 proj1-search-python3/layouts/minimaxClassic.lay create mode 100644 proj1-search-python3/layouts/oddSearch.lay create mode 100644 proj1-search-python3/layouts/openClassic.lay create mode 100644 proj1-search-python3/layouts/openMaze.lay create mode 100644 proj1-search-python3/layouts/openSearch.lay create mode 100644 proj1-search-python3/layouts/originalClassic.lay create mode 100644 proj1-search-python3/layouts/powerClassic.lay create mode 100644 proj1-search-python3/layouts/smallClassic.lay create mode 100644 proj1-search-python3/layouts/smallMaze.lay create mode 100644 proj1-search-python3/layouts/smallSafeSearch.lay create mode 100644 proj1-search-python3/layouts/smallSearch.lay create mode 100644 proj1-search-python3/layouts/testClassic.lay create mode 100644 proj1-search-python3/layouts/testMaze.lay create mode 100644 proj1-search-python3/layouts/testSearch.lay create mode 100644 proj1-search-python3/layouts/tinyCorners.lay create mode 100644 proj1-search-python3/layouts/tinyMaze.lay create mode 100644 proj1-search-python3/layouts/tinySafeSearch.lay create mode 100644 proj1-search-python3/layouts/tinySearch.lay create mode 100644 proj1-search-python3/layouts/trappedClassic.lay create mode 100644 proj1-search-python3/layouts/trickyClassic.lay create mode 100644 proj1-search-python3/layouts/trickySearch.lay create mode 100644 proj1-search-python3/pacman.py create mode 100644 proj1-search-python3/pacmanAgents.py create mode 100644 proj1-search-python3/projectParams.py create mode 100644 proj1-search-python3/search.py create mode 100644 proj1-search-python3/searchAgents.py create mode 100644 proj1-search-python3/searchTestClasses.py create mode 100644 proj1-search-python3/testClasses.py create mode 100644 proj1-search-python3/testParser.py create mode 100644 proj1-search-python3/test_cases/CONFIG create mode 100644 proj1-search-python3/test_cases/q1/CONFIG create mode 100644 proj1-search-python3/test_cases/q1/graph_backtrack.solution create mode 100644 proj1-search-python3/test_cases/q1/graph_backtrack.test create mode 100644 proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.solution create mode 100644 proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.test create mode 100644 proj1-search-python3/test_cases/q1/graph_infinite.solution create mode 100644 proj1-search-python3/test_cases/q1/graph_infinite.test create mode 100644 proj1-search-python3/test_cases/q1/graph_manypaths.solution create mode 100644 proj1-search-python3/test_cases/q1/graph_manypaths.test create mode 100644 proj1-search-python3/test_cases/q1/pacman_1.solution create mode 100644 proj1-search-python3/test_cases/q1/pacman_1.test create mode 100644 proj1-search-python3/test_cases/q2/CONFIG create mode 100644 proj1-search-python3/test_cases/q2/graph_backtrack.solution create mode 100644 proj1-search-python3/test_cases/q2/graph_backtrack.test create mode 100644 proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.solution create mode 100644 proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.test create mode 100644 proj1-search-python3/test_cases/q2/graph_infinite.solution create mode 100644 proj1-search-python3/test_cases/q2/graph_infinite.test create mode 100644 proj1-search-python3/test_cases/q2/graph_manypaths.solution create mode 100644 proj1-search-python3/test_cases/q2/graph_manypaths.test create mode 100644 proj1-search-python3/test_cases/q2/pacman_1.solution create mode 100644 proj1-search-python3/test_cases/q2/pacman_1.test create mode 100644 proj1-search-python3/test_cases/q3/CONFIG create mode 100644 proj1-search-python3/test_cases/q3/graph_backtrack.solution create mode 100644 proj1-search-python3/test_cases/q3/graph_backtrack.test create mode 100644 proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.solution create mode 100644 proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.test create mode 100644 proj1-search-python3/test_cases/q3/graph_infinite.solution create mode 100644 proj1-search-python3/test_cases/q3/graph_infinite.test create mode 100644 proj1-search-python3/test_cases/q3/graph_manypaths.solution create mode 100644 proj1-search-python3/test_cases/q3/graph_manypaths.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_0_graph.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_0_graph.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_1_problemC.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_1_problemC.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_2_problemE.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_2_problemE.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_3_problemW.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_3_problemW.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_4_testSearch.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_4_testSearch.test create mode 100644 proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.solution create mode 100644 proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.test create mode 100644 proj1-search-python3/test_cases/q4/CONFIG create mode 100644 proj1-search-python3/test_cases/q4/astar_0.solution create mode 100644 proj1-search-python3/test_cases/q4/astar_0.test create mode 100644 proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.solution create mode 100644 proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.test create mode 100644 proj1-search-python3/test_cases/q4/astar_2_manhattan.solution create mode 100644 proj1-search-python3/test_cases/q4/astar_2_manhattan.test create mode 100644 proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.solution create mode 100644 proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.test create mode 100644 proj1-search-python3/test_cases/q4/graph_backtrack.solution create mode 100644 proj1-search-python3/test_cases/q4/graph_backtrack.test create mode 100644 proj1-search-python3/test_cases/q4/graph_manypaths.solution create mode 100644 proj1-search-python3/test_cases/q4/graph_manypaths.test create mode 100644 proj1-search-python3/test_cases/q5/CONFIG create mode 100644 proj1-search-python3/test_cases/q5/corner_tiny_corner.solution create mode 100644 proj1-search-python3/test_cases/q5/corner_tiny_corner.test create mode 100644 proj1-search-python3/test_cases/q6/CONFIG create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_1.solution create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_1.test create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_2.solution create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_2.test create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_3.solution create mode 100644 proj1-search-python3/test_cases/q6/corner_sanity_3.test create mode 100644 proj1-search-python3/test_cases/q6/medium_corners.solution create mode 100644 proj1-search-python3/test_cases/q6/medium_corners.test create mode 100644 proj1-search-python3/test_cases/q7/CONFIG create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_1.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_1.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_10.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_10.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_11.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_11.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_12.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_12.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_13.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_13.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_14.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_14.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_15.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_15.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_16.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_16.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_17.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_17.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_2.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_2.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_3.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_3.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_4.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_4.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_5.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_5.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_6.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_6.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_7.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_7.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_8.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_8.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_9.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_9.test create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.solution create mode 100644 proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.test create mode 100644 proj1-search-python3/test_cases/q8/CONFIG create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_1.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_1.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_10.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_10.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_11.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_11.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_12.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_12.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_13.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_13.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_2.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_2.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_3.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_3.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_4.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_4.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_5.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_5.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_6.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_6.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_7.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_7.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_8.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_8.test create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_9.solution create mode 100644 proj1-search-python3/test_cases/q8/closest_dot_9.test create mode 100644 proj1-search-python3/textDisplay.py create mode 100644 proj1-search-python3/util.py create mode 100644 search/VERSION create mode 100644 search/autograder.py create mode 100644 search/commands.txt create mode 100644 search/eightpuzzle.py create mode 100644 search/game.py create mode 100644 search/game.pyc create mode 100644 search/ghostAgents.py create mode 100644 search/ghostAgents.pyc create mode 100644 search/grading.py create mode 100644 search/grading.pyc create mode 100644 search/graphicsDisplay.py create mode 100644 search/graphicsDisplay.pyc create mode 100644 search/graphicsUtils.py create mode 100644 search/graphicsUtils.pyc create mode 100644 search/keyboardAgents.py create mode 100644 search/keyboardAgents.pyc create mode 100644 search/layout.py create mode 100644 search/layout.pyc create mode 100644 search/layouts/bigCorners.lay create mode 100644 search/layouts/bigMaze.lay create mode 100644 search/layouts/bigSafeSearch.lay create mode 100644 search/layouts/bigSearch.lay create mode 100644 search/layouts/boxSearch.lay create mode 100644 search/layouts/capsuleClassic.lay create mode 100644 search/layouts/contestClassic.lay create mode 100644 search/layouts/contoursMaze.lay create mode 100644 search/layouts/greedySearch.lay create mode 100644 search/layouts/mediumClassic.lay create mode 100644 search/layouts/mediumCorners.lay create mode 100644 search/layouts/mediumDottedMaze.lay create mode 100644 search/layouts/mediumMaze.lay create mode 100644 search/layouts/mediumSafeSearch.lay create mode 100644 search/layouts/mediumScaryMaze.lay create mode 100644 search/layouts/mediumSearch.lay create mode 100644 search/layouts/minimaxClassic.lay create mode 100644 search/layouts/oddSearch.lay create mode 100644 search/layouts/openClassic.lay create mode 100644 search/layouts/openMaze.lay create mode 100644 search/layouts/openSearch.lay create mode 100644 search/layouts/originalClassic.lay create mode 100644 search/layouts/powerClassic.lay create mode 100644 search/layouts/smallClassic.lay create mode 100644 search/layouts/smallMaze.lay create mode 100644 search/layouts/smallSafeSearch.lay create mode 100644 search/layouts/smallSearch.lay create mode 100644 search/layouts/testClassic.lay create mode 100644 search/layouts/testMaze.lay create mode 100644 search/layouts/testSearch.lay create mode 100644 search/layouts/tinyCorners.lay create mode 100644 search/layouts/tinyMaze.lay create mode 100644 search/layouts/tinySafeSearch.lay create mode 100644 search/layouts/tinySearch.lay create mode 100644 search/layouts/trappedClassic.lay create mode 100644 search/layouts/trickyClassic.lay create mode 100644 search/layouts/trickySearch.lay create mode 100644 search/pacman.py create mode 100644 search/pacman.pyc create mode 100644 search/pacmanAgents.py create mode 100644 search/pacmanAgents.pyc create mode 100644 search/projectParams.py create mode 100644 search/projectParams.pyc create mode 100644 search/search.py create mode 100644 search/search.pyc create mode 100644 search/searchAgents.py create mode 100644 search/searchAgents.pyc create mode 100644 search/searchTestClasses.py create mode 100644 search/searchTestClasses.pyc create mode 100644 search/submission_autograder.py create mode 100644 search/testClasses.py create mode 100644 search/testClasses.pyc create mode 100644 search/testParser.py create mode 100644 search/testParser.pyc create mode 100644 search/test_cases/CONFIG create mode 100644 search/test_cases/q1/CONFIG create mode 100644 search/test_cases/q1/graph_backtrack.solution create mode 100644 search/test_cases/q1/graph_backtrack.test create mode 100644 search/test_cases/q1/graph_bfs_vs_dfs.solution create mode 100644 search/test_cases/q1/graph_bfs_vs_dfs.test create mode 100644 search/test_cases/q1/graph_infinite.solution create mode 100644 search/test_cases/q1/graph_infinite.test create mode 100644 search/test_cases/q1/graph_manypaths.solution create mode 100644 search/test_cases/q1/graph_manypaths.test create mode 100644 search/test_cases/q1/pacman_1.solution create mode 100644 search/test_cases/q1/pacman_1.test create mode 100644 search/test_cases/q2/CONFIG create mode 100644 search/test_cases/q2/graph_backtrack.solution create mode 100644 search/test_cases/q2/graph_backtrack.test create mode 100644 search/test_cases/q2/graph_bfs_vs_dfs.solution create mode 100644 search/test_cases/q2/graph_bfs_vs_dfs.test create mode 100644 search/test_cases/q2/graph_infinite.solution create mode 100644 search/test_cases/q2/graph_infinite.test create mode 100644 search/test_cases/q2/graph_manypaths.solution create mode 100644 search/test_cases/q2/graph_manypaths.test create mode 100644 search/test_cases/q2/pacman_1.solution create mode 100644 search/test_cases/q2/pacman_1.test create mode 100644 search/test_cases/q3/CONFIG create mode 100644 search/test_cases/q3/graph_backtrack.solution create mode 100644 search/test_cases/q3/graph_backtrack.test create mode 100644 search/test_cases/q3/graph_bfs_vs_dfs.solution create mode 100644 search/test_cases/q3/graph_bfs_vs_dfs.test create mode 100644 search/test_cases/q3/graph_infinite.solution create mode 100644 search/test_cases/q3/graph_infinite.test create mode 100644 search/test_cases/q3/graph_manypaths.solution create mode 100644 search/test_cases/q3/graph_manypaths.test create mode 100644 search/test_cases/q3/ucs_0_graph.solution create mode 100644 search/test_cases/q3/ucs_0_graph.test create mode 100644 search/test_cases/q3/ucs_1_problemC.solution create mode 100644 search/test_cases/q3/ucs_1_problemC.test create mode 100644 search/test_cases/q3/ucs_2_problemE.solution create mode 100644 search/test_cases/q3/ucs_2_problemE.test create mode 100644 search/test_cases/q3/ucs_3_problemW.solution create mode 100644 search/test_cases/q3/ucs_3_problemW.test create mode 100644 search/test_cases/q3/ucs_4_testSearch.solution create mode 100644 search/test_cases/q3/ucs_4_testSearch.test create mode 100644 search/test_cases/q3/ucs_5_goalAtDequeue.solution create mode 100644 search/test_cases/q3/ucs_5_goalAtDequeue.test create mode 100644 search/test_cases/q4/CONFIG create mode 100644 search/test_cases/q4/astar_0.solution create mode 100644 search/test_cases/q4/astar_0.test create mode 100644 search/test_cases/q4/astar_1_graph_heuristic.solution create mode 100644 search/test_cases/q4/astar_1_graph_heuristic.test create mode 100644 search/test_cases/q4/astar_2_manhattan.solution create mode 100644 search/test_cases/q4/astar_2_manhattan.test create mode 100644 search/test_cases/q4/astar_3_goalAtDequeue.solution create mode 100644 search/test_cases/q4/astar_3_goalAtDequeue.test create mode 100644 search/test_cases/q4/graph_backtrack.solution create mode 100644 search/test_cases/q4/graph_backtrack.test create mode 100644 search/test_cases/q4/graph_manypaths.solution create mode 100644 search/test_cases/q4/graph_manypaths.test create mode 100644 search/test_cases/q5/CONFIG create mode 100644 search/test_cases/q5/corner_tiny_corner.solution create mode 100644 search/test_cases/q5/corner_tiny_corner.test create mode 100644 search/test_cases/q6/CONFIG create mode 100644 search/test_cases/q6/corner_sanity_1.solution create mode 100644 search/test_cases/q6/corner_sanity_1.test create mode 100644 search/test_cases/q6/corner_sanity_2.solution create mode 100644 search/test_cases/q6/corner_sanity_2.test create mode 100644 search/test_cases/q6/corner_sanity_3.solution create mode 100644 search/test_cases/q6/corner_sanity_3.test create mode 100644 search/test_cases/q6/medium_corners.solution create mode 100644 search/test_cases/q6/medium_corners.test create mode 100644 search/test_cases/q7/CONFIG create mode 100644 search/test_cases/q7/food_heuristic_1.solution create mode 100644 search/test_cases/q7/food_heuristic_1.test create mode 100644 search/test_cases/q7/food_heuristic_10.solution create mode 100644 search/test_cases/q7/food_heuristic_10.test create mode 100644 search/test_cases/q7/food_heuristic_11.solution create mode 100644 search/test_cases/q7/food_heuristic_11.test create mode 100644 search/test_cases/q7/food_heuristic_12.solution create mode 100644 search/test_cases/q7/food_heuristic_12.test create mode 100644 search/test_cases/q7/food_heuristic_13.solution create mode 100644 search/test_cases/q7/food_heuristic_13.test create mode 100644 search/test_cases/q7/food_heuristic_14.solution create mode 100644 search/test_cases/q7/food_heuristic_14.test create mode 100644 search/test_cases/q7/food_heuristic_15.solution create mode 100644 search/test_cases/q7/food_heuristic_15.test create mode 100644 search/test_cases/q7/food_heuristic_16.solution create mode 100644 search/test_cases/q7/food_heuristic_16.test create mode 100644 search/test_cases/q7/food_heuristic_17.solution create mode 100644 search/test_cases/q7/food_heuristic_17.test create mode 100644 search/test_cases/q7/food_heuristic_2.solution create mode 100644 search/test_cases/q7/food_heuristic_2.test create mode 100644 search/test_cases/q7/food_heuristic_3.solution create mode 100644 search/test_cases/q7/food_heuristic_3.test create mode 100644 search/test_cases/q7/food_heuristic_4.solution create mode 100644 search/test_cases/q7/food_heuristic_4.test create mode 100644 search/test_cases/q7/food_heuristic_5.solution create mode 100644 search/test_cases/q7/food_heuristic_5.test create mode 100644 search/test_cases/q7/food_heuristic_6.solution create mode 100644 search/test_cases/q7/food_heuristic_6.test create mode 100644 search/test_cases/q7/food_heuristic_7.solution create mode 100644 search/test_cases/q7/food_heuristic_7.test create mode 100644 search/test_cases/q7/food_heuristic_8.solution create mode 100644 search/test_cases/q7/food_heuristic_8.test create mode 100644 search/test_cases/q7/food_heuristic_9.solution create mode 100644 search/test_cases/q7/food_heuristic_9.test create mode 100644 search/test_cases/q7/food_heuristic_grade_tricky.solution create mode 100644 search/test_cases/q7/food_heuristic_grade_tricky.test create mode 100644 search/test_cases/q8/CONFIG create mode 100644 search/test_cases/q8/closest_dot_1.solution create mode 100644 search/test_cases/q8/closest_dot_1.test create mode 100644 search/test_cases/q8/closest_dot_10.solution create mode 100644 search/test_cases/q8/closest_dot_10.test create mode 100644 search/test_cases/q8/closest_dot_11.solution create mode 100644 search/test_cases/q8/closest_dot_11.test create mode 100644 search/test_cases/q8/closest_dot_12.solution create mode 100644 search/test_cases/q8/closest_dot_12.test create mode 100644 search/test_cases/q8/closest_dot_13.solution create mode 100644 search/test_cases/q8/closest_dot_13.test create mode 100644 search/test_cases/q8/closest_dot_2.solution create mode 100644 search/test_cases/q8/closest_dot_2.test create mode 100644 search/test_cases/q8/closest_dot_3.solution create mode 100644 search/test_cases/q8/closest_dot_3.test create mode 100644 search/test_cases/q8/closest_dot_4.solution create mode 100644 search/test_cases/q8/closest_dot_4.test create mode 100644 search/test_cases/q8/closest_dot_5.solution create mode 100644 search/test_cases/q8/closest_dot_5.test create mode 100644 search/test_cases/q8/closest_dot_6.solution create mode 100644 search/test_cases/q8/closest_dot_6.test create mode 100644 search/test_cases/q8/closest_dot_7.solution create mode 100644 search/test_cases/q8/closest_dot_7.test create mode 100644 search/test_cases/q8/closest_dot_8.solution create mode 100644 search/test_cases/q8/closest_dot_8.test create mode 100644 search/test_cases/q8/closest_dot_9.solution create mode 100644 search/test_cases/q8/closest_dot_9.test create mode 100644 search/textDisplay.py create mode 100644 search/textDisplay.pyc create mode 100644 search/util.py create mode 100644 search/util.pyc diff --git a/proj1-search-python3/VERSION b/proj1-search-python3/VERSION new file mode 100644 index 0000000..7c7fa2f --- /dev/null +++ b/proj1-search-python3/VERSION @@ -0,0 +1 @@ +v1.001 diff --git a/proj1-search-python3/__pycache__/game.cpython-36.pyc b/proj1-search-python3/__pycache__/game.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..07856ab513e1c11092358c3729d49f0a4366fe02 GIT binary patch literal 20955 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/game.cpython-39.pyc b/proj1-search-python3/__pycache__/game.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..768dc55f89294d57b8f1a9788c604eedf5fbad96 GIT binary patch literal 20959 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/ghostAgents.cpython-39.pyc b/proj1-search-python3/__pycache__/ghostAgents.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..b61c33a24129b14691fcdc930173e0a2490b91c3 GIT binary patch literal 3275 zcmai0&5Inz6|bu9nd$l156Nrm%Zb~L>|_#aoDf42BP1T_v)kH z_j?sAEHoI#Xm#&{^DV}{B4T#fAg*9$IY7ul9Ev1gWIv~+1Rhd zl|j|3PSz@3|{T|mR20^kFCU&D`5=+IJ)u9R+{C8eUI)>2jMiYVrFE=f)~t(X*SqBzD^=XNhm*Q z3vFjI-c)rQxyMu!fB*3T*52wqy!+{+&-c0y{`i*%fBkd!n=cL@eewH;f4KkXZ(nx5 zzJLG0-~aURv-{ud{-fJVL)l&ZwI9nsB~jAv4pqASvkMu>-qwZT?R+aue%|dL$(ePA zx3%N@Q4;08e+tYpBRL;E_e7b_bbn4O3x{bMt$qxoUX{-IDW=3?90 zV7YxzDa0Y)Fj}|8y5hLeuIi<}%*hEVy17H%(pD@JMd76FjqC4S(>9i4{X%amO*8qn zN(Zy2+m<4G>I%G=OKperC9+!y5UEVp!J&+Vvmi4Efbj-z@v0cD%qV$uY);EL-_iGS zcEArAf)%>7#pu2FWxk`5%njVD=}wYM<;nzJfbFqlo-(JC;*3Ydw&2 zgKT&OGdm6NB$tp!BD72%Q36+V^Zg(R(}59w^vhMZPhkop1-VAv(trWF2x)o+>h?Is>3c6v65HU%SVMZh)ciyYR+#%B{!wY+&p#FPPXM<3wo%%ue+&oH)AQ?kGEvv zX1zeku=D8i&uBcp+#@NcgK!EU<3S3yvUtK<%;$v#D5#Js%4?JZs-3!tq`K)-f$x2& zmkd$zcpE&YZlRNk6jeVUa4e-L{@=hf!OeITztNfF%Kq=dNRFl4AZuo5YmgX)k!_pO zIEg#LE#Q!!*7rprwrvzldu)ed-zseNR%q>8cdShwqRs3%g|)_Z`M7dNV5MqSDrTjM zuT&1%KEK1UQ!_hNvs1%YewSB^+AiNG{yV8bMIG6(GHw=5zEIS6`8ifJiYBt;pCr!>7dxOU>nOiq7S)mERQ*5ZNcxdL-DD&g<@g6g^EZf z)+fSgbHASc7F|A7_k||bCL6u^lyi?I zUgu(*_JTOO1Y~;s+0ws`ZyM>(k0Eym3l(@moMERJC~hdgQ*3jh5tJcIsPhC&EWQQ| z(p$qcdm_7PWA!qfp^p0$`wA9~@hDLY`$aMynQnL1bczmwC6yla6T-P(9Gcv`mSzz( z;$u81{hsDY_NN3jJYNK2c&>EC7_Wdr++}zH*ae?)n)xj9lsM~Z*$q91`XoKWAV&SB z%<_$1s^l>VeuEoEg7%VLfNxB>*`U5}l3o#ey*%BRs-c@@=3kEnQt1=eE=$nSzBLFw zF%>{tgD5ctL0h9}Xo|f$M@*Lpd=H@AdQ#DK!?Y(OfY&lzY0?KgC(1TVN%Iy;1ST&S%s5Z|h%gFm(*fQD zl2Ldt(ZVyQ#T&xrjyTCrqDP-Z@kg%~4hq_7i~7xL)N$PZ7ya}ud6_s6XjhN5AoUV$ znK#2Tp9?OIT)><>7i`|BC|YYbwB5&ZL{avej4q#l)v}+LexRyg9v7x@nR{h;%$IHL YTrA%+m&j+?GC*kp2WD`@^3t;NU$tlAiU0rr literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/grading.cpython-36.pyc b/proj1-search-python3/__pycache__/grading.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e11604d1840094a55d53dd442b3cee6c0fcf7ebb GIT binary patch literal 9402 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/grading.cpython-39.pyc b/proj1-search-python3/__pycache__/grading.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5fc0bec6c5de0dfac5f55479dce1ef9f7227adae GIT binary patch literal 9478 zcmb7K+ix7#d7t~v&Mub}DN&*<+hfUcw2nl|cA7W>tw@$cNwq{N78BWPI-Aj+AvxsU zcxKiTN4w1fmBJ|E)Iot34f;S9y+A-4pnYfq7yU=3kFD(p=u6S|B`B)?zB3o@E=jwZ z#hf{p?|kQ+?|kRGopnY=ED6uTcFpJ?pljf#PC^+})q)yT_CG2L7x4N& zN8(DDvqba7Twh`;(;mr~t|=2(Fh@io~9E8&}GqihUc ziydbN*f<~yY=Rv`O_3d9llYFX!|VvYC3b=xWzPX(l)cQ3vFDK+V=u55(RP5@Sl`LW zxJqmB!&?_^<^+!I2fR}YIy_!%o3~coT40L>`=@QE-fAw}_q||+Xwh(jz(ZNkCTQ8r z^V@Z2!wy#5#$3(7a?^+MM2Aaw{XalL`|?QYDwm~AxhqwrK<+9crv&Pzgc>ctjtLlP zRU#<&YM-8q#7{MLN>ek(PqdU`+Hs5R5akE3M`y zff1aL0}b-w-&=O_Ty~edrt8xR#F}Y!ehte>_TEkil6A1tnt7+;o=XdM6Qw?w^^WTY zUaRR}Y_glq1J}O{Mmc9YcHQ#>yR~GWN+Cqg({{soV7FUdGw|)U%k8waoZ+m=AY%F` z$j}M7gV#TZWEnL1u-KEk;I%2~7s|SZb;_LjJeJBzq)eZ!C~looCz1U32gqGMxA?`! zzxeW(j}|}wi@*N-Z~toXH^18c@>hTI#h-ol<=_3&;=g_N+2{ZGmtXwpXTRC}=SAA_ z#f#Ugb=Tod5NVO<^4b|6#oEf5_C~PMYQC{ZT!Y5x_C{pQ(o$=!M*I9p4E3LQrSL?| zd(+jb*YtvFb$YhmsyTK4JTfWt|97Fw(vlJ{bZ1p%h3gn6lGk_YII$wnAvr}E6Uh^z z%M+?!m=9mN6F6LOH4gnrcIG6rodB70C(phfC*_GKpKgdBX>{fz9UJEQQSMT!(+pf5 z8S&;tX2bP;XW8{DxrB2fE$|v{lou-%S>2}*Bh7c3ZZul!aPBsP?wcuSIhdww0GnH1suYqzi8(8WvPq|0cmqV{M}UPN zCR7$-jYk-wS7fGJ^a23=qe!IOm~6?WJSiLUINtPUsp06*w$|w6rw`@ny@XfrDo&%+ zm8YaFNPulRlVIb=@;vtLIe)JyN`SdP)vw&k7yIAc?0+M326bd9atw$#f3gWk;n9_f-6V_h$yr^$am$ZuKO zk}QQEhewew1VxBEC@5xZD_t?xU8(t=CIurKU&vAq>MT6nm9|R2Wd@_$D&`?=jdhi- z+0|KYN}`&(()$uKd*<(%%@^YDS&TqGz#d?EBK!MNQ#lJrn#HFwKaR1-X&%k-q{dEb z&JxT7tr6dY^j*`@*_`dBmV>o}qpN{+aA5^QPT&mR%ca%X`v{HFtpY(q# zzx9NeT8R7bXRp5csy%;e;mSGtrnBbSKB;!-Y7)hDH*oE~p7uQ#THUqpxA+=qK1jg6 z*3%ZDy64uMjz83`!^JP$@E@$ zF{yY2nq8I~LQM5t+g_yajBS52%XRkzMEjXDfTKs`zG?3bNT}$EQ<2>pa0rZwy8$ok z`wY!;UpUb)0Q}XU)cTCzLmh~uUpL0U2p~Nu`)pn8A7YfR8)L6Ep*(d>wx3GtePH@|B-w z>@WcS732|P4gr4^_%2Y|&kZ@S3p~+T!2v?d6Wg+38 zbEmEHi=3=eUZmt0l1Qz)O+HCQVNY7jTUx@*nl(34+pRVy*&S(}z^g}jGJtQB1zj0k zfR*0r1apZ-j&jRx0C$&1`75bniY8!DW~H04r61)3?$q3SPHin3p?1z&{2F<3k+$Su zkV2=K4a3K1UL{IiqGX(s2_z9n0{tS~37GhuMr3^C)Zr7vmih?R%R05xSSJ%cI-JxA zi{wtN)lOQ=d0{+PMu_ZCtg$T;6*K*M(`iTMy;ig1-}Y*i@zgM2sV4L}Sre6l*UXyu z2^vuo`;_^R!n2BWvAb`g-DgN7!<38o9h5cMP{xomWkpsL>wlG}nx-fQ%9=bTD^E?e zB$wn@$emD3*?NjsnZ&Ef2hny!HWf=gE}$o$8fY`#lK2aJ1i|7e23 zxIc#s3^~Lex-bgKP(=xG2$55_lx=BCW$HGWo+UE>;gM+6w*Cqu|5*?JCOaE9U3j>R z?5H>lBCNQKqITl-okq0>E6#_}htna*=hcL*lbBH%!uzh>bYa1eWwz{1yME1S!%l;} zn6Z<6uM^n!xzipR3`U}3&%z-(ZwJnDhA})E8(0~G+T8Jcm%(Q7BlyJDd|BgffFzLx zHch$Liq})cS|T0M94{#Af`0@P@L9m~*Qk?5M{>yj;%CwF$9R2mt0lbz4{=-$k0#r* z$7vbZEMl{4vqBVtBrHVU7&c8kEcJAz^o*{&rI4q=FQmC%uA4(0whMJhE`g)E1p$W~ z>I{F8LVNJVb~YO!p)IXzip^dk2ehos#|u7>Y4GnMDeE|gIR09&(T3!JG~jPj+XZTi zj~FKgtBkmG;;%!5Igl}x!g}J#&vzOXgF4?@suZ(Ia?JCh2g!&XXdFsTOgl{B*HO2H zH}=8Bane)^bjFXqswQhnNe&P8(L9qt1F;mMHf@tQ8pjX<@uZ#{2;Ym20%)#?k~kPB zsR*bc+&YV(9lpw}#Ha@0%1OBIOq+$*EPQ4S$LD7~gfc;Ey=yQXJ)~85%kUQwh7~1v zw4wwoSsQ%5*q7|(n6at{=ncO)x2EtvhbL=J!9NxF5Gnv3_>A40|7(_S!q;SlPc+nt zIpA9aR&6WSHN<}YF2aE_8<~{?la<)$rt*i!dM0_<@T<*j`0aNk2Da0rO?b-9@z>z- z`=!)BexH0k;)k*Mitg8NiYs}9da5ED9uiIf0)%wBPr)CVfnQRN4CoDt*J$LZg|K*2 zNoqfAb{akV)WbMB)SGz-T)ndW5D1{yg5DE~SP1_plHcRRdu6SnqLWY*kqYoiE&;A+ zIPHk|1PCqq;SnMGFq5`h<*pA;%y*|({Z=!Q@0Sbw+k{u>neR|es2x&~krAJFUKRzX z-FBNS(txEIX`;KvI*oR!LvmPIl_B0$66&eiZu!1LxSUW(OsNW3ehp|VCY7mJ zu{^(;PM>fsdG%c~{g_H`#pA6ahA4@12 zQ!UD;=)u3`$W4TY%9zxboBqA05c!T2#9&Pa^)qiWm)H?-&wMpR6|<`9$yK#poi!v;JeTo)WDG zxdO#Au+~wgq-#a2iPkkjtL%qLPC$47vB(|nS_v9;eiPIS^;zb9bUvJ#_1g|^O6mw* zymg$+1;N$FXU>O*2b%gCKFIXd?8+PGvx1Fh;BY2V5+M<$~9wY6LPJ${|)XxGCLp*2tXvooYUg_w6h94VUytt(v*G4b*K8P57RvWO=P z)b(}nS6V!v<2BH$UmC3MDGQQ$_Oe;n_UsH1IIHNN7G22f8suRbcHTiUjACYJ_5Mh^pmkEG^wq+|eHz*=4b}rsS8b2~tT)KCQ=SHF>p4(N*k>L<0BEU}u=)d6g39%%! zBbk?)Fd$)gk`ek_!?a*j7KM=sGZS(JZ$S~V2I+qo6KF%pLR+R$2Tbo>q3z6mp+Gro z7F=zSAsNRKVWCiv8#+f^E~??T?zp(&LEzsDHf+J5_C0ttWLDxD#6=v9*$|UZCO&Mu zWK-wn!Ptj@J{rS8^Ol`+k3Amk@Juv|-3{iWiY?uKQuUF>d%OxdNEt7zGEXNx;v@sPlP0_$faQ<*I=)1cp}_JBd;QPti*1Q>NJ9(#Yf_rK-{j8-ILCw|C&HF zKFbu|++zydp~(!cKPoa8o?CERjPPRL@Dm9k`rCyGus@HT2L6;1LR*ktA`1(4+M3Ew zf%9OqB^*aq47MpYDq>rJwkh7Q3zVZfz~Sj8EH>Av*JBntPE!_4gF^w|YjFQ|;C9Vg z1$JR;-aw|S_}`|P^YH+-ywF6ry6@sXC5ggI{4`oL(W2un1^Nj382S@0Y(Z#2p`nCk zQszUALemf(DNYclNRf;TRs- zwX9@xNSwL3xw1TmTRCDNksYU7!T2sBvZ~eiexh29iq-0R$EhbRxoVZQYSk)#k*4%A zC2vu3mJ;%2I2nALdLlK&c6=KN!U?MM)H;lC z!jDYLoHS3GZ^AZI>1XLyffF=g9(YK=T)-<(4E#q!VJiMDp~e3c!T(7N9HSf`1#I}d zP{?$ZA75Q0e*&@3iuwVLApDt;d5Suh0*Wu-sBSBFaWrwEr)=PC(GQn(@*U}{kgWVi z0LIpi;1d<9RrsCIu?>*fe8=4o!6od8xE)nJ_MneGf;4q>`yTBw4K6|Hm7wmziR79Z z7w;Lq{RMszJ;@U!Ew+h?mF4ienJMzFi~ou=<3CIwOhx}?sL>s`FD`%RerwIeFaSkizHIOn>grLlO34}}v{+$7^x@p$656tJ zDZ_(HG}9281YzX3C(J literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/graphicsDisplay.cpython-39.pyc b/proj1-search-python3/__pycache__/graphicsDisplay.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a952766b4ebf930b222f63389cf50b4fc7288ba5 GIT binary patch literal 20410 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 fe3tZuG2Ht^OF|;KGe4-lVd+vZJhrDXPbm= literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/graphicsUtils.cpython-39.pyc b/proj1-search-python3/__pycache__/graphicsUtils.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..53aa3a9af7c35abd67ab5b64a08116bb98950d96 GIT binary patch literal 10460 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/keyboardAgents.cpython-39.pyc b/proj1-search-python3/__pycache__/keyboardAgents.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ae216cc03864aca5958ab6e3f032f69fc278bc78 GIT binary patch literal 2176 zcma)7&u`;I6rQoij^ie6frWxpAX=dnfo%l~rv+6lv;wqCy6O)hOeD*j@g`0kyF25y z+bTV^LY$B|@k0o85kJ%eD{(^VihpFT&~`a;;LdyFq)lK~FxEVOGw;icJ>Tb-pj>td zv>ToMkNzAXVkEa!6}cOW?aHw!E@*0O3SCd2raCirkh=OU-qx0U1jQ4(do{~YWm zZ3x70s7-gC8al)2K5ghA3c$v<(+BY_08X`C=q}oYaucnyYf_S#U1N{zQMj9UpCG%e z%bH}(6j!?pkE=@$tS)KWcj+$N4+>oiklnR2r(Ik#L>qE$*V-gGf(&VoKxZ)XF>-WH zBm5PNsQ4l)9hCd)z#eYb26wQU78DFw!n60cf+&OYIvcNxP|A>3N4;`W1naG+Ay=~~ zmf{I`KXCzqVsRuhF^X`k8D`hRW)RQi&ejsD0?eYyT6zH=91$gCJ7@y`+QNs*7D2+( zjyiN`UZA@rjRJf40~bdSKgkG_`3btq$u z_AbMgyCfjsKBEQfDRnk^<4HTNa&i4 uHEdXRfw`)!SQ88=Q0n~Kc?J>MMH3#js&d}UtvZ9HOga#-4mrm7D}Mv)-}LwZ literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/layout.cpython-36.pyc b/proj1-search-python3/__pycache__/layout.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e36c8b8133d5a217cf418100942740b9ee8517f6 GIT binary patch literal 5606 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/pacman.cpython-36.pyc b/proj1-search-python3/__pycache__/pacman.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9958be4fc2080a26ed3cebc3b4b50deaed4f1b25 GIT binary patch literal 22826 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/pacman.cpython-39.pyc b/proj1-search-python3/__pycache__/pacman.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f9e0d6bca53a44f65352f72be7a59ae164535095 GIT binary patch literal 22913 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

{lE4U0kh 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/projectParams.cpython-39.pyc b/proj1-search-python3/__pycache__/projectParams.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fafbf8ed2ca80eecd419cdbc39089223390d364d GIT binary patch literal 348 zcmYe~<>g`k0#2uvXgy{IhQ}ZdGGGL99Dul31W2SXL@}f=Mlq%^MKPr?M=__c1T$!| zR!J78CKe@UIHsrOl@#k0RO&$ZKvtCqgc*`rT;iOQSX`V6lCKgBD9X=DO)gO|v{DEL z>vi+fWVt0091`l{>K794?C;_l@8as_80r&pOC%u3-^xe2WJr z>gVX|dW+M^-!C*cKETs?B|{M_(4}DFYa)>4ViohOW8(8!Eiq4*EPuLoS z{%p>!=Nos&JlVDD>Gq}1X6|~?yf-E}KP5HB(K9{==%l>Ny!4m?pfe41Azsies4U6I m&oc%K0^J^vSd^Fx^35#{o80`A(wtN~eo#2E0SOjH5Ci~V25@=+ literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/search.cpython-36.pyc b/proj1-search-python3/__pycache__/search.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0621a16601cc407110f60846753745d68ccd1145 GIT binary patch literal 5524 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/search.cpython-39.pyc b/proj1-search-python3/__pycache__/search.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..6ad039771b35961c2d78b6886909752daa5eea2c GIT binary patch literal 5516 zcmb7I-H#hr6`wELV|%^%AZi=6glSs3ak^cBpdg|sWNDhVqTL2IsKQ$0j^|$6(~f8C zJ2RW^YW#p2B!v0^FGWC$vIvSQL?w7Z3h;kaBP1RmY5QF9&hOki9)ARt*4Es)bLW1Y z-#O=Z&Ku5}8Mxb}}XEbIq>!!x6*Nu+B zDy(`_>zJ&@Y}_qYXARsdtjX4Jud)-Y#nx}?of@z8b#{y$N8RR?PMuqw2Ddv+UhS+g zV^?dRJWLwTg-*;};SIOO2b<18lsY&3AaMNAIN&25CeDC|T=-tm%W;E2B>ZGJik%xn z-y1rv;Ev}80cTF{pmELhMsDc17?s4%Ie#|$wrs!}?XAW~kWbt3s9gLRc({x!{t^nV zb#$gdS{;%a%#@N^tRf|?vYM3CW_8?atN~e?d23h7aZN-$Z2j<`4F|vLLqB%Bz>Q-k zN|V43dF&)Z?!<{my(GonqrT(Lgd3}|n@%rH92W67+(_n7$O+x>fQHZI1_u`$0;Go(T`y640SI!k?*ncYIboDIA*WH1;|<+#z%k?? z8T#SC0fML$GHg10pNlOy*3$vg>_>IN?c#DD|@Al=B)uwb`xwIMZ(XJz$Ce59X_~Z z09#A_JlTi(;YamPLTgl_*B$A=+>%!$I_fa=Q12wG^*b@tuB zXU-sU12xG4vcklE-ZexOx4a_NA(2#tO|4Boeg;FO$d95yiCjHAy)5!Vug@Z|`#kLD z)5?4mhT=K{f1ks*Nd=qAK;vPQ2FwxCzR)A^<3xIj@IfW_=fqLlk^#iVyzw`z9Pw(X{DUt(*E-s5_TI+F>=NUx3b zsjh38{)#a*P?}RSGw`Ro@fX?6s5q!-{;HQz|w>0J$2x60AW43^tM^G$6OlVwz zzbWOkt=t*_ZgC2)^2+Xu-@E>7ZoTyM?sdGW6`@AG)Z<*gu>x$qJ`+JHcAH=${ItrN zUd3M^ozj*NCJhjsN`!xd$|AxlHcpL!MhM?BGVPXmqz$ybaYXnVx2z*AeiQe~k+x?{ zt!V{4s+o19i|4b-u9jJuxvz`!XsuD}w3?Z>Y^q^&^@{d`$M(!=ZE8>JSxxo~! z6CQ|?7Yrq-kn<%8dqKRIOF4Fc$I8Z8S}R>PfXdGc5KZ6IqD}- zuIUZk!o7k2x^C+a=;9oDEV(kVZ|TZ!;-PR~9qtR7yvKcY>ApX~8|A*{)ROLN!hJ7i z7Tgz35BEJI-50HJZv*a&8r-*1xNnslIIGH@FUwUx09kFpeP5oODcx5k*-t7-m6;B4 zB#Lf)3eTPCF|T|lgQ`9f6@=p?M)5Xu4su4vVDm( zk*}{!EoK5&Nxp!S|I0p`8k|PHQ093aj8=liI98o5AG$LPM9OF3|>^kuFh# zE^X-YS&(pDj&I;jf)@V{1(k9*J{xkN)Gw{A&gaRvQOW@%B{&im`NqZO4`p%9Olg*;T`C| zc6ffle#rM!j^u*;7g8`q_e&a}fJ?c@E-o1tGlV-ir?K-9y^zXBNY-ClH3R!%;EsCC zy>t`ZKE|aTK9Yt$qWXx^E487mi{$n}d2~rghDgdL06{?i%1J3WrMJYvW$Yb=*iZP%j>vl8AN3# z_4|w}TGn*&Rm^Zt`cBBn+=LjMDf;-8j6&Pakf#D?Cwy}07vMRd5q|{vRA`b%5pecM zGED)3I*ZB3iYOT(2f&~*j?WSYM67+EAw(+8UQ^#YkQvJ4f4(vzY9jo79~pq0L+V?Z zks&0iGJLF%N-KmSo}F#~4-ieAQ!C=hRS<~Lh{hxce@3NX^_8S7GyC`ibY$pA&%on` zcnAKC^bBcP1<9Ls3+wajBK9=b|T1^*+V>Ctx* zb%dlmW{$J+W{GYY2U<%`wBR`NP~ZTtg~N!92W!$_@_ISBc$j+0fH02_6dF49^2my$ zK)ENyh)y%%qj7Pd>ewNFO`fqd~fAGaI!S^JcIzEN9yfSlOU{|m!6l8FES literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/searchAgents.cpython-36.pyc b/proj1-search-python3/__pycache__/searchAgents.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..fa0db1ad8a53a3dd18d8bbbad171061ce6eaffbc GIT binary patch literal 21309 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 EA6o?xtN;K2 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/testClasses.cpython-36.pyc b/proj1-search-python3/__pycache__/testClasses.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..96a7c783a88d925883eff1ec4379cbc8c7b6b2ff GIT binary patch literal 6649 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/testParser.cpython-39.pyc b/proj1-search-python3/__pycache__/testParser.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..cc2c84914cb56774fc05187bd1af5534fb541a7f GIT binary patch literal 2035 zcmZ`4ZHpU4cxHBXv&kjb(3a~R771w&nxidN1>q>#+7m0tAt;FHw#%BCyX-aD zaDuG15Ka#U#3h*VX#kQmsUS@zs9?*~b{!8DI$XH8kK<42-wSpK>JU}f2R_{nFFzTWX3eEY+L zpTGAW-y1)=_r=3+b|3xvyZ7hr?t|aHd-&DvbYv8PWtKan4J6I$oMG;g{J$T}Ia zl&+BLFAuHM${+?~I^-!A>?Tu7Da&YTjVUFYZjK8pa^kO-l_+^R+M%+6nr72`k^|g;B#+81|;Cg4>-il5ulpE!>vEH-x4gZG~dnxG@K4 zuAWu0+q)&N^txRcCGkA);#mNMaq7|%%rZMp2Mbg0KX{^rMomT$5ErKL!j~Wk$&e-_ zB^z`^KYboN!ZJ1{nPt3Ty$>7=KBC(N-~Z<0J(pGvWyPeU6Oal}S0Pp!JY%QHohrT~%0ly5jqg zFL{mtinWFnAiG!rCgBDJ@3I#!c};D>&OHqG;G}o?%(WZ!>r1aSmO)m}iALS*+rygC zgQcjQ4`F-30%B4r?ES#8d5g$~iYR{-&Z8#XCDCkPx*CpTY_QX~P@v`r4 zZUvoOq6%Pu@rpWw7`CV>faR|+R7YXPNV&}A?u_9b=xx|mP?xFdTY#j{%@zpswzkKH z4Eh^0UF}RtGm^sJ;KY6x13rgtcNO`jURsPxkY1%h`Sn25COHki75lad6RC@D zY`0`T342k4YZrsO61EbB!PERE_E#=~BskGR>EyHk literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/textDisplay.cpython-36.pyc b/proj1-search-python3/__pycache__/textDisplay.cpython-36.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5b476661b57f87a117cdedd5ef06fcd27a21f06c GIT binary patch literal 2946 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/__pycache__/util.cpython-39.pyc b/proj1-search-python3/__pycache__/util.cpython-39.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0e1b234953cdb923bd901cf580225f20206b69c6 GIT binary patch literal 23568 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 literal 0 HcmV?d00001 diff --git a/proj1-search-python3/autograder.py b/proj1-search-python3/autograder.py new file mode 100644 index 0000000..d966fc4 --- /dev/null +++ b/proj1-search-python3/autograder.py @@ -0,0 +1,370 @@ +# autograder.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# imports from python standard library +import py_compile +import pprint +import grading +import imp +import optparse +import os +import re +import sys +import projectParams +import random +random.seed(0) +try: + from pacman import GameState +except: + pass + +# register arguments and set default values + + +def readCommand(argv): + parser = optparse.OptionParser( + description='Run public tests on student code') + parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, + muteOutput=False, printTestCase=False, noGraphics=False) + parser.add_option('--test-directory', + dest='testRoot', + default='test_cases', + help='Root test directory which contains subdirectories corresponding to each question') + parser.add_option('--student-code', + dest='studentCode', + default=projectParams.STUDENT_CODE_DEFAULT, + help='comma separated list of student code files') + parser.add_option('--code-directory', + dest='codeRoot', + default="", + help='Root directory containing the student and testClass code') + parser.add_option('--test-case-code', + dest='testCaseCode', + default=projectParams.PROJECT_TEST_CLASSES, + help='class containing testClass classes for this project') + parser.add_option('--generate-solutions', + dest='generateSolutions', + action='store_true', + help='Write solutions generated to .solution file') + parser.add_option('--edx-output', + dest='edxOutput', + action='store_true', + help='Generate edX output files') + parser.add_option('--gradescope-output', + dest='gsOutput', + action='store_true', + help='Generate GradeScope output files') + parser.add_option('--mute', + dest='muteOutput', + action='store_true', + help='Mute output from executing tests') + parser.add_option('--print-tests', '-p', + dest='printTestCase', + action='store_true', + help='Print each test case before running them.') + parser.add_option('--test', '-t', + dest='runTest', + default=None, + help='Run one particular test. Relative to test root.') + parser.add_option('--question', '-q', + dest='gradeQuestion', + default=None, + help='Grade one particular question.') + parser.add_option('--no-graphics', + dest='noGraphics', + action='store_true', + help='No graphics display for pacman games.') + (options, args) = parser.parse_args(argv) + return options + + +# confirm we should author solution files +def confirmGenerate(): + print('WARNING: this action will overwrite any solution files.') + print('Are you sure you want to proceed? (yes/no)') + while True: + ans = sys.stdin.readline().strip() + if ans == 'yes': + break + elif ans == 'no': + sys.exit(0) + else: + print('please answer either "yes" or "no"') + + +# TODO: Fix this so that it tracebacks work correctly +# Looking at source of the traceback module, presuming it works +# the same as the intepreters, it uses co_filename. This is, +# however, a readonly attribute. +def setModuleName(module, filename): + functionType = type(confirmGenerate) + classType = type(optparse.Option) + + for i in dir(module): + o = getattr(module, i) + if hasattr(o, '__file__'): + continue + + if type(o) == functionType: + setattr(o, '__file__', filename) + elif type(o) == classType: + setattr(o, '__file__', filename) + # TODO: assign member __file__'s? + #print(i, type(o)) + + +#from cStringIO import StringIO + +def loadModuleString(moduleSource): + # Below broken, imp doesn't believe its being passed a file: + # ValueError: load_module arg#2 should be a file or None + # + #f = StringIO(moduleCodeDict[k]) + #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE)) + tmp = imp.new_module(k) + exec(moduleCodeDict[k] in tmp.__dict__) + setModuleName(tmp, k) + return tmp + + +def loadModuleFile(moduleName, filePath): + with open(filePath, 'r') as f: + return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE)) + + +def readFile(path, root=""): + "Read file from disk at specified path and return as string" + with open(os.path.join(root, path), 'r') as handle: + return handle.read() + + +####################################################################### +# Error Hint Map +####################################################################### + +# TODO: use these +ERROR_HINT_MAP = { + 'q1': { + "": """ + We noticed that your project threw an IndexError on q1. + While many things may cause this, it may have been from + assuming a certain number of successors from a state space + or assuming a certain number of actions available from a given + state. Try making your code more general (no hardcoded indices) + and submit again! + """ + }, + 'q3': { + "": """ + We noticed that your project threw an AttributeError on q3. + While many things may cause this, it may have been from assuming + a certain size or structure to the state space. For example, if you have + a line of code assuming that the state is (x, y) and we run your code + on a state space with (x, y, z), this error could be thrown. Try + making your code more general and submit again! + + """ + } +} + + +def splitStrings(d): + d2 = dict(d) + for k in d: + if k[0:2] == "__": + del d2[k] + continue + if d2[k].find("\n") >= 0: + d2[k] = d2[k].split("\n") + return d2 + + +def printTest(testDict, solutionDict): + pp = pprint.PrettyPrinter(indent=4) + print("Test case:") + for line in testDict["__raw_lines__"]: + print(" |", line) + print("Solution:") + for line in solutionDict["__raw_lines__"]: + print(" |", line) + + +def runTest(testName, moduleDict, printTestCase=False, display=None): + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + testDict = testParser.TestParser(testName + ".test").parse() + solutionDict = testParser.TestParser(testName + ".solution").parse() + test_out_file = os.path.join('%s.test_output' % testName) + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + + questionClass = getattr(testClasses, 'Question') + question = questionClass({'max_points': 0}, display) + testCase = testClass(question, testDict) + + if printTestCase: + printTest(testDict, solutionDict) + + # This is a fragile hack to create a stub grades object + grades = grading.Grades(projectParams.PROJECT_NAME, [(None, 0)]) + testCase.execute(grades, moduleDict, solutionDict) + + +# returns all the tests you need to run in order to run question +def getDepends(testParser, testRoot, question): + allDeps = [question] + questionDict = testParser.TestParser( + os.path.join(testRoot, question, 'CONFIG')).parse() + if 'depends' in questionDict: + depends = questionDict['depends'].split() + for d in depends: + # run dependencies first + allDeps = getDepends(testParser, testRoot, d) + allDeps + return allDeps + +# get list of questions to grade + + +def getTestSubdirs(testParser, testRoot, questionToGrade): + problemDict = testParser.TestParser( + os.path.join(testRoot, 'CONFIG')).parse() + if questionToGrade != None: + questions = getDepends(testParser, testRoot, questionToGrade) + if len(questions) > 1: + print('Note: due to dependencies, the following tests will be run: %s' % + ' '.join(questions)) + return questions + if 'order' in problemDict: + return problemDict['order'].split() + return sorted(os.listdir(testRoot)) + + +# evaluate student code +def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, + edxOutput=False, muteOutput=False, gsOutput=False, + printTestCase=False, questionToGrade=None, display=None): + # imports of testbench code. note that the testClasses import must follow + # the import of student code due to dependencies + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + questions = [] + questionDicts = {} + test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade) + for q in test_subdirs: + subdir_path = os.path.join(testRoot, q) + if not os.path.isdir(subdir_path) or q[0] == '.': + continue + + # create a question object + questionDict = testParser.TestParser( + os.path.join(subdir_path, 'CONFIG')).parse() + questionClass = getattr(testClasses, questionDict['class']) + question = questionClass(questionDict, display) + questionDicts[q] = questionDict + + # load test cases into question + tests = filter(lambda t: re.match( + '[^#~.].*\.test\Z', t), os.listdir(subdir_path)) + tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests) + for t in sorted(tests): + test_file = os.path.join(subdir_path, '%s.test' % t) + solution_file = os.path.join(subdir_path, '%s.solution' % t) + test_out_file = os.path.join(subdir_path, '%s.test_output' % t) + testDict = testParser.TestParser(test_file).parse() + if testDict.get("disabled", "false").lower() == "true": + continue + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + testCase = testClass(question, testDict) + + def makefun(testCase, solution_file): + if generateSolutions: + # write solution file to disk + return lambda grades: testCase.writeSolution(moduleDict, solution_file) + else: + # read in solution dictionary and pass as an argument + testDict = testParser.TestParser(test_file).parse() + solutionDict = testParser.TestParser(solution_file).parse() + if printTestCase: + return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict) + else: + return lambda grades: testCase.execute(grades, moduleDict, solutionDict) + question.addTestCase(testCase, makefun(testCase, solution_file)) + + # Note extra function is necessary for scoping reasons + def makefun(question): + return lambda grades: question.execute(grades) + setattr(sys.modules[__name__], q, makefun(question)) + questions.append((q, question.getMaxPoints())) + + grades = grading.Grades(projectParams.PROJECT_NAME, questions, + gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput) + if questionToGrade == None: + for q in questionDicts: + for prereq in questionDicts[q].get('depends', '').split(): + grades.addPrereq(q, prereq) + + grades.grade(sys.modules[__name__], bonusPic=projectParams.BONUS_PIC) + return grades.points + + +def getDisplay(graphicsByDefault, options=None): + graphics = graphicsByDefault + if options is not None and options.noGraphics: + graphics = False + if graphics: + try: + import graphicsDisplay + return graphicsDisplay.PacmanGraphics(1, frameTime=.05) + except ImportError: + pass + import textDisplay + return textDisplay.NullGraphics() + + +if __name__ == '__main__': + options = readCommand(sys.argv) + if options.generateSolutions: + confirmGenerate() + codePaths = options.studentCode.split(',') + # moduleCodeDict = {} + # for cp in codePaths: + # moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot) + # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot) + # moduleDict = loadModuleDict(moduleCodeDict) + + moduleDict = {} + for cp in codePaths: + moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + moduleDict[moduleName] = loadModuleFile( + moduleName, os.path.join(options.codeRoot, cp)) + moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1) + moduleDict['projectTestClasses'] = loadModuleFile( + moduleName, os.path.join(options.codeRoot, options.testCaseCode)) + + if options.runTest != None: + runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, + display=getDisplay(True, options)) + else: + evaluate(options.generateSolutions, options.testRoot, moduleDict, + gsOutput=options.gsOutput, + edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase, + questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion != None, options)) diff --git a/proj1-search-python3/commands.txt b/proj1-search-python3/commands.txt new file mode 100644 index 0000000..21683ac --- /dev/null +++ b/proj1-search-python3/commands.txt @@ -0,0 +1,21 @@ +python pacman.py +python pacman.py --layout testMaze --pacman GoWestAgent +python pacman.py --layout tinyMaze --pacman GoWestAgent +python pacman.py -h +python pacman.py -l tinyMaze -p SearchAgent -a fn=tinyMazeSearch +python pacman.py -l tinyMaze -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent -a fn=bfs +python pacman.py -l bigMaze -p SearchAgent -a fn=bfs -z .5 +python eightpuzzle.py +python pacman.py -l mediumMaze -p SearchAgent -a fn=ucs +python pacman.py -l mediumDottedMaze -p StayEastSearchAgent +python pacman.py -l mediumScaryMaze -p StayWestSearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent -a fn=astar,heuristic=manhattanHeuristic +python pacman.py -l tinyCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5 +python pacman.py -l testSearch -p AStarFoodSearchAgent +python pacman.py -l trickySearch -p AStarFoodSearchAgent +python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5 diff --git a/proj1-search-python3/eightpuzzle.py b/proj1-search-python3/eightpuzzle.py new file mode 100644 index 0000000..bb8ea6f --- /dev/null +++ b/proj1-search-python3/eightpuzzle.py @@ -0,0 +1,281 @@ +# eightpuzzle.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import search +import random + +# Module Classes + +class EightPuzzleState: + """ + The Eight Puzzle is described in the course textbook on + page 64. + + This class defines the mechanics of the puzzle itself. The + task of recasting this puzzle as a search problem is left to + the EightPuzzleSearchProblem class. + """ + + def __init__( self, numbers ): + """ + Constructs a new eight puzzle from an ordering of numbers. + + numbers: a list of integers from 0 to 8 representing an + instance of the eight puzzle. 0 represents the blank + space. Thus, the list + + [1, 0, 2, 3, 4, 5, 6, 7, 8] + + represents the eight puzzle: + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------ + + The configuration of the puzzle is stored in a 2-dimensional + list (a list of lists) 'cells'. + """ + self.cells = [] + numbers = numbers[:] # Make a copy so as not to cause side-effects. + numbers.reverse() + for row in range( 3 ): + self.cells.append( [] ) + for col in range( 3 ): + self.cells[row].append( numbers.pop() ) + if self.cells[row][col] == 0: + self.blankLocation = row, col + + def isGoal( self ): + """ + Checks to see if the puzzle is in its goal state. + + ------------- + | | 1 | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).isGoal() + True + + >>> EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).isGoal() + False + """ + current = 0 + for row in range( 3 ): + for col in range( 3 ): + if current != self.cells[row][col]: + return False + current += 1 + return True + + def legalMoves( self ): + """ + Returns a list of legal moves from the current state. + + Moves consist of moving the blank space up, down, left or right. + These are encoded as 'up', 'down', 'left' and 'right' respectively. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).legalMoves() + ['down', 'right'] + """ + moves = [] + row, col = self.blankLocation + if(row != 0): + moves.append('up') + if(row != 2): + moves.append('down') + if(col != 0): + moves.append('left') + if(col != 2): + moves.append('right') + return moves + + def result(self, move): + """ + Returns a new eightPuzzle with the current state and blankLocation + updated based on the provided move. + + The move should be a string drawn from a list returned by legalMoves. + Illegal moves will raise an exception, which may be an array bounds + exception. + + NOTE: This function *does not* change the current object. Instead, + it returns a new object. + """ + row, col = self.blankLocation + if(move == 'up'): + newrow = row - 1 + newcol = col + elif(move == 'down'): + newrow = row + 1 + newcol = col + elif(move == 'left'): + newrow = row + newcol = col - 1 + elif(move == 'right'): + newrow = row + newcol = col + 1 + else: + raise "Illegal Move" + + # Create a copy of the current eightPuzzle + newPuzzle = EightPuzzleState([0, 0, 0, 0, 0, 0, 0, 0, 0]) + newPuzzle.cells = [values[:] for values in self.cells] + # And update it to reflect the move + newPuzzle.cells[row][col] = self.cells[newrow][newcol] + newPuzzle.cells[newrow][newcol] = self.cells[row][col] + newPuzzle.blankLocation = newrow, newcol + + return newPuzzle + + # Utilities for comparison and display + def __eq__(self, other): + """ + Overloads '==' such that two eightPuzzles with the same configuration + are equal. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]) == \ + EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).result('left') + True + """ + for row in range( 3 ): + if self.cells[row] != other.cells[row]: + return False + return True + + def __hash__(self): + return hash(str(self.cells)) + + def __getAsciiString(self): + """ + Returns a display string for the maze + """ + lines = [] + horizontalLine = ('-' * (13)) + lines.append(horizontalLine) + for row in self.cells: + rowLine = '|' + for col in row: + if col == 0: + col = ' ' + rowLine = rowLine + ' ' + col.__str__() + ' |' + lines.append(rowLine) + lines.append(horizontalLine) + return '\n'.join(lines) + + def __str__(self): + return self.__getAsciiString() + +# TODO: Implement The methods in this class + +class EightPuzzleSearchProblem(search.SearchProblem): + """ + Implementation of a SearchProblem for the Eight Puzzle domain + + Each state is represented by an instance of an eightPuzzle. + """ + def __init__(self,puzzle): + "Creates a new EightPuzzleSearchProblem which stores search information." + self.puzzle = puzzle + + def getStartState(self): + return puzzle + + def isGoalState(self,state): + return state.isGoal() + + def getSuccessors(self,state): + """ + Returns list of (successor, action, stepCost) pairs where + each succesor is either left, right, up, or down + from the original state and the cost is 1.0 for each + """ + succ = [] + for a in state.legalMoves(): + succ.append((state.result(a), a, 1)) + return succ + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. The sequence must + be composed of legal moves + """ + return len(actions) + +EIGHT_PUZZLE_DATA = [[1, 0, 2, 3, 4, 5, 6, 7, 8], + [1, 7, 8, 2, 3, 4, 5, 6, 0], + [4, 3, 2, 7, 0, 5, 1, 6, 8], + [5, 1, 3, 4, 0, 2, 6, 7, 8], + [1, 2, 5, 7, 6, 8, 0, 4, 3], + [0, 3, 1, 6, 8, 2, 7, 5, 4]] + +def loadEightPuzzle(puzzleNumber): + """ + puzzleNumber: The number of the eight puzzle to load. + + Returns an eight puzzle object generated from one of the + provided puzzles in EIGHT_PUZZLE_DATA. + + puzzleNumber can range from 0 to 5. + + >>> print(loadEightPuzzle(0)) + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + """ + return EightPuzzleState(EIGHT_PUZZLE_DATA[puzzleNumber]) + +def createRandomEightPuzzle(moves=100): + """ + moves: number of random moves to apply + + Creates a random eight puzzle by applying + a series of 'moves' random moves to a solved + puzzle. + """ + puzzle = EightPuzzleState([0,1,2,3,4,5,6,7,8]) + for i in range(moves): + # Execute a random legal move + puzzle = puzzle.result(random.sample(puzzle.legalMoves(), 1)[0]) + return puzzle + +if __name__ == '__main__': + puzzle = createRandomEightPuzzle(25) + print('A random puzzle:') + print(puzzle) + + problem = EightPuzzleSearchProblem(puzzle) + path = search.breadthFirstSearch(problem) + print('BFS found a path of %d moves: %s' % (len(path), str(path))) + curr = puzzle + i = 1 + for a in path: + curr = curr.result(a) + print('After %d move%s: %s' % (i, ("", "s")[i>1], a)) + print(curr) + + input("Press return for the next state...") # wait for key stroke + i += 1 diff --git a/proj1-search-python3/game.py b/proj1-search-python3/game.py new file mode 100644 index 0000000..3950f27 --- /dev/null +++ b/proj1-search-python3/game.py @@ -0,0 +1,729 @@ +# game.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# game.py +# ------- +# Licensing Information: Please do not distribute or publish solutions to this +# project. You are free to use and extend these projects for educational +# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by +# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html + +from util import * +import time, os +import traceback +import sys + +####################### +# Parts worth reading # +####################### + +class Agent: + """ + An agent must define a getAction method, but may also define the + following methods which will be called if they exist: + + def registerInitialState(self, state): # inspects the starting state + """ + def __init__(self, index=0): + self.index = index + + def getAction(self, state): + """ + The Agent will receive a GameState (from either {pacman, capture, sonar}.py) and + must return an action from Directions.{North, South, East, West, Stop} + """ + raiseNotDefined() + +class Directions: + NORTH = 'North' + SOUTH = 'South' + EAST = 'East' + WEST = 'West' + STOP = 'Stop' + + LEFT = {NORTH: WEST, + SOUTH: EAST, + EAST: NORTH, + WEST: SOUTH, + STOP: STOP} + + RIGHT = dict([(y,x) for x, y in LEFT.items()]) + + REVERSE = {NORTH: SOUTH, + SOUTH: NORTH, + EAST: WEST, + WEST: EAST, + STOP: STOP} + +class Configuration: + """ + A Configuration holds the (x,y) coordinate of a character, along with its + traveling direction. + + The convention for positions, like a graph, is that (0,0) is the lower left corner, x increases + horizontally and y increases vertically. Therefore, north is the direction of increasing y, or (0,1). + """ + + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def getPosition(self): + return (self.pos) + + def getDirection(self): + return self.direction + + def isInteger(self): + x,y = self.pos + return x == int(x) and y == int(y) + + def __eq__(self, other): + if other == None: return False + return (self.pos == other.pos and self.direction == other.direction) + + def __hash__(self): + x = hash(self.pos) + y = hash(self.direction) + return hash(x + 13 * y) + + def __str__(self): + return "(x,y)="+str(self.pos)+", "+str(self.direction) + + def generateSuccessor(self, vector): + """ + Generates a new configuration reached by translating the current + configuration by the action vector. This is a low-level call and does + not attempt to respect the legality of the movement. + + Actions are movement vectors. + """ + x, y= self.pos + dx, dy = vector + direction = Actions.vectorToDirection(vector) + if direction == Directions.STOP: + direction = self.direction # There is no stop direction + return Configuration((x + dx, y+dy), direction) + +class AgentState: + """ + AgentStates hold the state of an agent (configuration, speed, scared, etc). + """ + + def __init__( self, startConfiguration, isPacman ): + self.start = startConfiguration + self.configuration = startConfiguration + self.isPacman = isPacman + self.scaredTimer = 0 + self.numCarrying = 0 + self.numReturned = 0 + + def __str__( self ): + if self.isPacman: + return "Pacman: " + str( self.configuration ) + else: + return "Ghost: " + str( self.configuration ) + + def __eq__( self, other ): + if other == None: + return False + return self.configuration == other.configuration and self.scaredTimer == other.scaredTimer + + def __hash__(self): + return hash(hash(self.configuration) + 13 * hash(self.scaredTimer)) + + def copy( self ): + state = AgentState( self.start, self.isPacman ) + state.configuration = self.configuration + state.scaredTimer = self.scaredTimer + state.numCarrying = self.numCarrying + state.numReturned = self.numReturned + return state + + def getPosition(self): + if self.configuration == None: return None + return self.configuration.getPosition() + + def getDirection(self): + return self.configuration.getDirection() + +class Grid: + """ + A 2-dimensional array of objects backed by a list of lists. Data is accessed + via grid[x][y] where (x,y) are positions on a Pacman map with x horizontal, + y vertical and the origin (0,0) in the bottom left corner. + + The __str__ method constructs an output that is oriented like a pacman board. + """ + def __init__(self, width, height, initialValue=False, bitRepresentation=None): + if initialValue not in [False, True]: raise Exception('Grids can only contain booleans') + self.CELLS_PER_INT = 30 + + self.width = width + self.height = height + self.data = [[initialValue for y in range(height)] for x in range(width)] + if bitRepresentation: + self._unpackBits(bitRepresentation) + + def __getitem__(self, i): + return self.data[i] + + def __setitem__(self, key, item): + self.data[key] = item + + def __str__(self): + out = [[str(self.data[x][y])[0] for x in range(self.width)] for y in range(self.height)] + out.reverse() + return '\n'.join([''.join(x) for x in out]) + + def __eq__(self, other): + if other == None: return False + return self.data == other.data + + def __hash__(self): + # return hash(str(self)) + base = 1 + h = 0 + for l in self.data: + for i in l: + if i: + h += base + base *= 2 + return hash(h) + + def copy(self): + g = Grid(self.width, self.height) + g.data = [x[:] for x in self.data] + return g + + def deepCopy(self): + return self.copy() + + def shallowCopy(self): + g = Grid(self.width, self.height) + g.data = self.data + return g + + def count(self, item =True ): + return sum([x.count(item) for x in self.data]) + + def asList(self, key = True): + list = [] + for x in range(self.width): + for y in range(self.height): + if self[x][y] == key: list.append( (x,y) ) + return list + + def packBits(self): + """ + Returns an efficient int list representation + + (width, height, bitPackedInts...) + """ + bits = [self.width, self.height] + currentInt = 0 + for i in range(self.height * self.width): + bit = self.CELLS_PER_INT - (i % self.CELLS_PER_INT) - 1 + x, y = self._cellIndexToPosition(i) + if self[x][y]: + currentInt += 2 ** bit + if (i + 1) % self.CELLS_PER_INT == 0: + bits.append(currentInt) + currentInt = 0 + bits.append(currentInt) + return tuple(bits) + + def _cellIndexToPosition(self, index): + x = index // self.height + y = index % self.height + return x, y + + def _unpackBits(self, bits): + """ + Fills in data from a bit-level representation + """ + cell = 0 + for packed in bits: + for bit in self._unpackInt(packed, self.CELLS_PER_INT): + if cell == self.width * self.height: break + x, y = self._cellIndexToPosition(cell) + self[x][y] = bit + cell += 1 + + def _unpackInt(self, packed, size): + bools = [] + if packed < 0: raise ValueError("must be a positive integer") + for i in range(size): + n = 2 ** (self.CELLS_PER_INT - i - 1) + if packed >= n: + bools.append(True) + packed -= n + else: + bools.append(False) + return bools + +def reconstituteGrid(bitRep): + if type(bitRep) is not type((1,2)): + return bitRep + width, height = bitRep[:2] + return Grid(width, height, bitRepresentation= bitRep[2:]) + +#################################### +# Parts you shouldn't have to read # +#################################### + +class Actions: + """ + A collection of static methods for manipulating move actions. + """ + # Directions + _directions = {Directions.NORTH: (0, 1), + Directions.SOUTH: (0, -1), + Directions.EAST: (1, 0), + Directions.WEST: (-1, 0), + Directions.STOP: (0, 0)} + + _directionsAsList = _directions.items() + + TOLERANCE = .001 + + def reverseDirection(action): + if action == Directions.NORTH: + return Directions.SOUTH + if action == Directions.SOUTH: + return Directions.NORTH + if action == Directions.EAST: + return Directions.WEST + if action == Directions.WEST: + return Directions.EAST + return action + reverseDirection = staticmethod(reverseDirection) + + def vectorToDirection(vector): + dx, dy = vector + if dy > 0: + return Directions.NORTH + if dy < 0: + return Directions.SOUTH + if dx < 0: + return Directions.WEST + if dx > 0: + return Directions.EAST + return Directions.STOP + vectorToDirection = staticmethod(vectorToDirection) + + def directionToVector(direction, speed = 1.0): + dx, dy = Actions._directions[direction] + return (dx * speed, dy * speed) + directionToVector = staticmethod(directionToVector) + + def getPossibleActions(config, walls): + possible = [] + x, y = config.pos + x_int, y_int = int(x + 0.5), int(y + 0.5) + + # In between grid points, all agents must continue straight + if (abs(x - x_int) + abs(y - y_int) > Actions.TOLERANCE): + return [config.getDirection()] + + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_y = y_int + dy + next_x = x_int + dx + if not walls[next_x][next_y]: possible.append(dir) + + return possible + + getPossibleActions = staticmethod(getPossibleActions) + + def getLegalNeighbors(position, walls): + x,y = position + x_int, y_int = int(x + 0.5), int(y + 0.5) + neighbors = [] + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_x = x_int + dx + if next_x < 0 or next_x == walls.width: continue + next_y = y_int + dy + if next_y < 0 or next_y == walls.height: continue + if not walls[next_x][next_y]: neighbors.append((next_x, next_y)) + return neighbors + getLegalNeighbors = staticmethod(getLegalNeighbors) + + def getSuccessor(position, action): + dx, dy = Actions.directionToVector(action) + x, y = position + return (x + dx, y + dy) + getSuccessor = staticmethod(getSuccessor) + +class GameStateData: + """ + + """ + def __init__( self, prevState = None ): + """ + Generates a new data packet by copying information from its predecessor. + """ + if prevState != None: + self.food = prevState.food.shallowCopy() + self.capsules = prevState.capsules[:] + self.agentStates = self.copyAgentStates( prevState.agentStates ) + self.layout = prevState.layout + self._eaten = prevState._eaten + self.score = prevState.score + + self._foodEaten = None + self._foodAdded = None + self._capsuleEaten = None + self._agentMoved = None + self._lose = False + self._win = False + self.scoreChange = 0 + + def deepCopy( self ): + state = GameStateData( self ) + state.food = self.food.deepCopy() + state.layout = self.layout.deepCopy() + state._agentMoved = self._agentMoved + state._foodEaten = self._foodEaten + state._foodAdded = self._foodAdded + state._capsuleEaten = self._capsuleEaten + return state + + def copyAgentStates( self, agentStates ): + copiedStates = [] + for agentState in agentStates: + copiedStates.append( agentState.copy() ) + return copiedStates + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + if other == None: return False + # TODO Check for type of other + if not self.agentStates == other.agentStates: return False + if not self.food == other.food: return False + if not self.capsules == other.capsules: return False + if not self.score == other.score: return False + return True + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + for i, state in enumerate( self.agentStates ): + try: + int(hash(state)) + except TypeError as e: + print(e) + #hash(state) + return int((hash(tuple(self.agentStates)) + 13*hash(self.food) + 113* hash(tuple(self.capsules)) + 7 * hash(self.score)) % 1048575 ) + + def __str__( self ): + width, height = self.layout.width, self.layout.height + map = Grid(width, height) + if type(self.food) == type((1,2)): + self.food = reconstituteGrid(self.food) + for x in range(width): + for y in range(height): + food, walls = self.food, self.layout.walls + map[x][y] = self._foodWallStr(food[x][y], walls[x][y]) + + for agentState in self.agentStates: + if agentState == None: continue + if agentState.configuration == None: continue + x,y = [int( i ) for i in nearestPoint( agentState.configuration.pos )] + agent_dir = agentState.configuration.direction + if agentState.isPacman: + map[x][y] = self._pacStr( agent_dir ) + else: + map[x][y] = self._ghostStr( agent_dir ) + + for x, y in self.capsules: + map[x][y] = 'o' + + return str(map) + ("\nScore: %d\n" % self.score) + + def _foodWallStr( self, hasFood, hasWall ): + if hasFood: + return '.' + elif hasWall: + return '%' + else: + return ' ' + + def _pacStr( self, dir ): + if dir == Directions.NORTH: + return 'v' + if dir == Directions.SOUTH: + return '^' + if dir == Directions.WEST: + return '>' + return '<' + + def _ghostStr( self, dir ): + return 'G' + if dir == Directions.NORTH: + return 'M' + if dir == Directions.SOUTH: + return 'W' + if dir == Directions.WEST: + return '3' + return 'E' + + def initialize( self, layout, numGhostAgents ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.food = layout.food.copy() + #self.capsules = [] + self.capsules = layout.capsules[:] + self.layout = layout + self.score = 0 + self.scoreChange = 0 + + self.agentStates = [] + numGhosts = 0 + for isPacman, pos in layout.agentPositions: + if not isPacman: + if numGhosts == numGhostAgents: continue # Max ghosts reached already + else: numGhosts += 1 + self.agentStates.append( AgentState( Configuration( pos, Directions.STOP), isPacman) ) + self._eaten = [False for a in self.agentStates] + +try: + import boinc + _BOINC_ENABLED = True +except: + _BOINC_ENABLED = False + +class Game: + """ + The Game manages the control flow, soliciting actions from agents. + """ + + def __init__( self, agents, display, rules, startingIndex=0, muteAgents=False, catchExceptions=False ): + self.agentCrashed = False + self.agents = agents + self.display = display + self.rules = rules + self.startingIndex = startingIndex + self.gameOver = False + self.muteAgents = muteAgents + self.catchExceptions = catchExceptions + self.moveHistory = [] + self.totalAgentTimes = [0 for agent in agents] + self.totalAgentTimeWarnings = [0 for agent in agents] + self.agentTimeout = False + import io + self.agentOutput = [io.StringIO() for agent in agents] + + def getProgress(self): + if self.gameOver: + return 1.0 + else: + return self.rules.getProgress(self) + + def _agentCrash( self, agentIndex, quiet=False): + "Helper method for handling agent crashes" + if not quiet: traceback.print_exc() + self.gameOver = True + self.agentCrashed = True + self.rules.agentCrash(self, agentIndex) + + OLD_STDOUT = None + OLD_STDERR = None + + def mute(self, agentIndex): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + import io + OLD_STDOUT = sys.stdout + OLD_STDERR = sys.stderr + sys.stdout = self.agentOutput[agentIndex] + sys.stderr = self.agentOutput[agentIndex] + + def unmute(self): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + # Revert stdout/stderr to originals + sys.stdout = OLD_STDOUT + sys.stderr = OLD_STDERR + + + def run( self ): + """ + Main control loop for game play. + """ + self.display.initialize(self.state.data) + self.numMoves = 0 + + ###self.display.initialize(self.state.makeObservation(1).data) + # inform learning agents of the game start + for i in range(len(self.agents)): + agent = self.agents[i] + if not agent: + self.mute(i) + # this is a null agent, meaning it failed to load + # the other team wins + print("Agent %d failed to load" % i, file=sys.stderr) + self.unmute() + self._agentCrash(i, quiet=True) + return + if ("registerInitialState" in dir(agent)): + self.mute(i) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.registerInitialState, int(self.rules.getMaxStartupTime(i))) + try: + start_time = time.time() + timed_func(self.state.deepCopy()) + time_taken = time.time() - start_time + self.totalAgentTimes[i] += time_taken + except TimeoutFunctionException: + print("Agent %d ran out of time on startup!" % i, file=sys.stderr) + self.unmute() + self.agentTimeout = True + self._agentCrash(i, quiet=True) + return + except Exception as data: + self._agentCrash(i, quiet=False) + self.unmute() + return + else: + agent.registerInitialState(self.state.deepCopy()) + ## TODO: could this exceed the total time + self.unmute() + + agentIndex = self.startingIndex + numAgents = len( self.agents ) + + while not self.gameOver: + # Fetch the next agent + agent = self.agents[agentIndex] + move_time = 0 + skip_action = False + # Generate an observation of the state + if 'observationFunction' in dir( agent ): + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.observationFunction, int(self.rules.getMoveTimeout(agentIndex))) + try: + start_time = time.time() + observation = timed_func(self.state.deepCopy()) + except TimeoutFunctionException: + skip_action = True + move_time += time.time() - start_time + self.unmute() + except Exception as data: + self._agentCrash(agentIndex, quiet=False) + self.unmute() + return + else: + observation = agent.observationFunction(self.state.deepCopy()) + self.unmute() + else: + observation = self.state.deepCopy() + + # Solicit an action + action = None + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.getAction, int(self.rules.getMoveTimeout(agentIndex)) - int(move_time)) + try: + start_time = time.time() + if skip_action: + raise TimeoutFunctionException() + action = timed_func( observation ) + except TimeoutFunctionException: + print("Agent %d timed out on a single move!" % agentIndex, file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + move_time += time.time() - start_time + + if move_time > self.rules.getMoveWarningTime(agentIndex): + self.totalAgentTimeWarnings[agentIndex] += 1 + print("Agent %d took too long to make a move! This is warning %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + if self.totalAgentTimeWarnings[agentIndex] > self.rules.getMaxTimeWarnings(agentIndex): + print("Agent %d exceeded the maximum number of warnings: %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + self.totalAgentTimes[agentIndex] += move_time + #print("Agent: %d, time: %f, total: %f" % (agentIndex, move_time, self.totalAgentTimes[agentIndex])) + if self.totalAgentTimes[agentIndex] > self.rules.getMaxTotalTime(agentIndex): + print("Agent %d ran out of time! (time: %1.2f)" % (agentIndex, self.totalAgentTimes[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + self.unmute() + except Exception as data: + self._agentCrash(agentIndex) + self.unmute() + return + else: + action = agent.getAction(observation) + self.unmute() + + # Execute the action + self.moveHistory.append( (agentIndex, action) ) + if self.catchExceptions: + try: + self.state = self.state.generateSuccessor( agentIndex, action ) + except Exception as data: + self.mute(agentIndex) + self._agentCrash(agentIndex) + self.unmute() + return + else: + self.state = self.state.generateSuccessor( agentIndex, action ) + + # Change the display + self.display.update( self.state.data ) + ###idx = agentIndex - agentIndex % 2 + 1 + ###self.display.update( self.state.makeObservation(idx).data ) + + # Allow for game specific conditions (winning, losing, etc.) + self.rules.process(self.state, self) + # Track progress + if agentIndex == numAgents + 1: self.numMoves += 1 + # Next agent + agentIndex = ( agentIndex + 1 ) % numAgents + + if _BOINC_ENABLED: + boinc.set_fraction_done(self.getProgress()) + + # inform a learning agent of the game result + for agentIndex, agent in enumerate(self.agents): + if "final" in dir( agent ) : + try: + self.mute(agentIndex) + agent.final( self.state ) + self.unmute() + except Exception as data: + if not self.catchExceptions: raise data + self._agentCrash(agentIndex) + self.unmute() + return + self.display.finish() diff --git a/proj1-search-python3/ghostAgents.py b/proj1-search-python3/ghostAgents.py new file mode 100644 index 0000000..c3afe1f --- /dev/null +++ b/proj1-search-python3/ghostAgents.py @@ -0,0 +1,81 @@ +# ghostAgents.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Actions +from game import Directions +import random +from util import manhattanDistance +import util + +class GhostAgent( Agent ): + def __init__( self, index ): + self.index = index + + def getAction( self, state ): + dist = self.getDistribution(state) + if len(dist) == 0: + return Directions.STOP + else: + return util.chooseFromDistribution( dist ) + + def getDistribution(self, state): + "Returns a Counter encoding a distribution over actions from the provided state." + util.raiseNotDefined() + +class RandomGhost( GhostAgent ): + "A ghost that chooses a legal action uniformly at random." + def getDistribution( self, state ): + dist = util.Counter() + for a in state.getLegalActions( self.index ): dist[a] = 1.0 + dist.normalize() + return dist + +class DirectionalGhost( GhostAgent ): + "A ghost that prefers to rush Pacman, or flee when scared." + def __init__( self, index, prob_attack=0.8, prob_scaredFlee=0.8 ): + self.index = index + self.prob_attack = prob_attack + self.prob_scaredFlee = prob_scaredFlee + + def getDistribution( self, state ): + # Read variables from state + ghostState = state.getGhostState( self.index ) + legalActions = state.getLegalActions( self.index ) + pos = state.getGhostPosition( self.index ) + isScared = ghostState.scaredTimer > 0 + + speed = 1 + if isScared: speed = 0.5 + + actionVectors = [Actions.directionToVector( a, speed ) for a in legalActions] + newPositions = [( pos[0]+a[0], pos[1]+a[1] ) for a in actionVectors] + pacmanPosition = state.getPacmanPosition() + + # Select best actions given the state + distancesToPacman = [manhattanDistance( pos, pacmanPosition ) for pos in newPositions] + if isScared: + bestScore = max( distancesToPacman ) + bestProb = self.prob_scaredFlee + else: + bestScore = min( distancesToPacman ) + bestProb = self.prob_attack + bestActions = [action for action, distance in zip( legalActions, distancesToPacman ) if distance == bestScore] + + # Construct distribution + dist = util.Counter() + for a in bestActions: dist[a] = bestProb / len(bestActions) + for a in legalActions: dist[a] += ( 1-bestProb ) / len(legalActions) + dist.normalize() + return dist diff --git a/proj1-search-python3/grading.py b/proj1-search-python3/grading.py new file mode 100644 index 0000000..1166127 --- /dev/null +++ b/proj1-search-python3/grading.py @@ -0,0 +1,329 @@ +# grading.py +# ---------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +"Common code for autograders" + +import cgi +import time +import sys +import json +import traceback +import pdb +from collections import defaultdict +import util + + +class Grades: + "A data structure for project grades, along with formatting code to display them" + + def __init__(self, projectName, questionsAndMaxesList, + gsOutput=False, edxOutput=False, muteOutput=False): + """ + Defines the grading scheme for a project + projectName: project name + questionsAndMaxesDict: a list of (question name, max points per question) + """ + self.questions = [el[0] for el in questionsAndMaxesList] + self.maxes = dict(questionsAndMaxesList) + self.points = Counter() + self.messages = dict([(q, []) for q in self.questions]) + self.project = projectName + self.start = time.localtime()[1:6] + self.sane = True # Sanity checks + self.currentQuestion = None # Which question we're grading + self.edxOutput = edxOutput + self.gsOutput = gsOutput # GradeScope output + self.mute = muteOutput + self.prereqs = defaultdict(set) + + #print('Autograder transcript for %s' % self.project) + print('Starting on %d-%d at %d:%02d:%02d' % self.start) + + def addPrereq(self, question, prereq): + self.prereqs[question].add(prereq) + + def grade(self, gradingModule, exceptionMap={}, bonusPic=False): + """ + Grades each question + gradingModule: the module with all the grading functions (pass in with sys.modules[__name__]) + """ + + completedQuestions = set([]) + for q in self.questions: + print('\nQuestion %s' % q) + print('=' * (9 + len(q))) + print + self.currentQuestion = q + + incompleted = self.prereqs[q].difference(completedQuestions) + if len(incompleted) > 0: + prereq = incompleted.pop() + print( + """*** NOTE: Make sure to complete Question %s before working on Question %s, +*** because Question %s builds upon your answer for Question %s. +""" % (prereq, q, q, prereq)) + continue + + if self.mute: + util.mutePrint() + try: + util.TimeoutFunction(getattr(gradingModule, q), 1800)( + self) # Call the question's function + # TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function + except Exception as inst: + self.addExceptionMessage(q, inst, traceback) + self.addErrorHints(exceptionMap, inst, q[1]) + except: + self.fail('FAIL: Terminated with a string exception.') + finally: + if self.mute: + util.unmutePrint() + + if self.points[q] >= self.maxes[q]: + completedQuestions.add(q) + + print('\n### Question %s: %d/%d ###\n' % + (q, self.points[q], self.maxes[q])) + + print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6]) + print("\nProvisional grades\n==================") + + for q in self.questions: + print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q])) + print('------------------') + print('Total: %d/%d' % + (self.points.totalCount(), sum(self.maxes.values()))) + if bonusPic and self.points.totalCount() == 25: + print(""" + + ALL HAIL GRANDPAC. + LONG LIVE THE GHOSTBUSTING KING. + + --- ---- --- + | \ / + \ / | + | + \--/ \--/ + | + | + + | + | + + + | + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@ + V @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + /\ @@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@ + /\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + +""") + print(""" +Your grades are NOT yet registered. To register your grades, make sure +to follow your instructor's guidelines to receive credit on your project. +""") + + if self.edxOutput: + self.produceOutput() + if self.gsOutput: + self.produceGradeScopeOutput() + + def addExceptionMessage(self, q, inst, traceback): + """ + Method to format the exception message, this is more complicated because + we need to cgi.escape the traceback but wrap the exception in a

 tag
+        """
+        self.fail('FAIL: Exception raised: %s' % inst)
+        self.addMessage('')
+        for line in traceback.format_exc().split('\n'):
+            self.addMessage(line)
+
+    def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+        typeOf = str(type(errorInstance))
+        questionName = 'q' + questionNum
+        errorHint = ''
+
+        # question specific error hints
+        if exceptionMap.get(questionName):
+            questionMap = exceptionMap.get(questionName)
+            if (questionMap.get(typeOf)):
+                errorHint = questionMap.get(typeOf)
+        # fall back to general error messages if a question specific
+        # one does not exist
+        if (exceptionMap.get(typeOf)):
+            errorHint = exceptionMap.get(typeOf)
+
+        # dont include the HTML if we have no error hint
+        if not errorHint:
+            return ''
+
+        for line in errorHint.split('\n'):
+            self.addMessage(line)
+
+    def produceGradeScopeOutput(self):
+        out_dct = {}
+
+        # total of entire submission
+        total_possible = sum(self.maxes.values())
+        total_score = sum(self.points.values())
+        out_dct['score'] = total_score
+        out_dct['max_score'] = total_possible
+        out_dct['output'] = "Total score (%d / %d)" % (
+            total_score, total_possible)
+
+        # individual tests
+        tests_out = []
+        for name in self.questions:
+            test_out = {}
+            # test name
+            test_out['name'] = name
+            # test score
+            test_out['score'] = self.points[name]
+            test_out['max_score'] = self.maxes[name]
+            # others
+            is_correct = self.points[name] >= self.maxes[name]
+            test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+                num=(name[1] if len(name) == 2 else name),
+                points=test_out['score'],
+                max=test_out['max_score'],
+                correct=('X' if not is_correct else ''),
+            )
+            test_out['tags'] = []
+            tests_out.append(test_out)
+        out_dct['tests'] = tests_out
+
+        # file output
+        with open('gradescope_response.json', 'w') as outfile:
+            json.dump(out_dct, outfile)
+        return
+
+    def produceOutput(self):
+        edxOutput = open('edx_response.html', 'w')
+        edxOutput.write("
") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '' + if (total_score >= total_possible): + checkOrX = '' + header = """ +

+ Total score ({total_score} / {total_possible}) +

+ """.format(total_score=total_score, + total_possible=total_possible, + checkOrX=checkOrX + ) + edxOutput.write(header) + + for q in self.questions: + if len(q) == 2: + name = q[1] + else: + name = q + checkOrX = '' + if (self.points[q] >= self.maxes[q]): + checkOrX = '' + #messages = '\n
\n'.join(self.messages[q]) + messages = "
%s
" % '\n'.join(self.messages[q]) + output = """ +
+
+
+ Question {q} ({points}/{max}) {checkOrX} +
+
+ {messages} +
+
+
+ """.format(q=name, + max=self.maxes[q], + messages=messages, + checkOrX=checkOrX, + points=self.points[q] + ) + # print("*** output for Question %s " % q[1]) + # print(output) + edxOutput.write(output) + edxOutput.write("
") + edxOutput.close() + edxOutput = open('edx_grade', 'w') + edxOutput.write(str(self.points.totalCount())) + edxOutput.close() + + def fail(self, message, raw=False): + "Sets sanity check bit to false and outputs a message" + self.sane = False + self.assignZeroCredit() + self.addMessage(message, raw) + + def assignZeroCredit(self): + self.points[self.currentQuestion] = 0 + + def addPoints(self, amt): + self.points[self.currentQuestion] += amt + + def deductPoints(self, amt): + self.points[self.currentQuestion] -= amt + + def assignFullCredit(self, message="", raw=False): + self.points[self.currentQuestion] = self.maxes[self.currentQuestion] + if message != "": + self.addMessage(message, raw) + + def addMessage(self, message, raw=False): + if not raw: + # We assume raw messages, formatted for HTML, are printed separately + if self.mute: + util.unmutePrint() + print('*** ' + message) + if self.mute: + util.mutePrint() + message = cgi.escape(message) + self.messages[self.currentQuestion].append(message) + + def addMessageToEmail(self, message): + print("WARNING**** addMessageToEmail is deprecated %s" % message) + for line in message.split('\n'): + pass + #print('%%% ' + line + ' %%%') + # self.messages[self.currentQuestion].append(line) + + +class Counter(dict): + """ + Dict with default 0 + """ + + def __getitem__(self, idx): + try: + return dict.__getitem__(self, idx) + except KeyError: + return 0 + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) diff --git a/proj1-search-python3/graphicsDisplay.py b/proj1-search-python3/graphicsDisplay.py new file mode 100644 index 0000000..bd53e9a --- /dev/null +++ b/proj1-search-python3/graphicsDisplay.py @@ -0,0 +1,679 @@ +# graphicsDisplay.py +# ------------------ +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from graphicsUtils import * +import math, time +from game import Directions + +########################### +# GRAPHICS DISPLAY CODE # +########################### + +# Most code by Dan Klein and John Denero written or rewritten for cs188, UC Berkeley. +# Some code from a Pacman implementation by LiveWires, and used / modified with permission. + +DEFAULT_GRID_SIZE = 30.0 +INFO_PANE_HEIGHT = 35 +BACKGROUND_COLOR = formatColor(0,0,0) +WALL_COLOR = formatColor(0.0/255.0, 51.0/255.0, 255.0/255.0) +INFO_PANE_COLOR = formatColor(.4,.4,0) +SCORE_COLOR = formatColor(.9, .9, .9) +PACMAN_OUTLINE_WIDTH = 2 +PACMAN_CAPTURE_OUTLINE_WIDTH = 4 + +GHOST_COLORS = [] +GHOST_COLORS.append(formatColor(.9,0,0)) # Red +GHOST_COLORS.append(formatColor(0,.3,.9)) # Blue +GHOST_COLORS.append(formatColor(.98,.41,.07)) # Orange +GHOST_COLORS.append(formatColor(.1,.75,.7)) # Green +GHOST_COLORS.append(formatColor(1.0,0.6,0.0)) # Yellow +GHOST_COLORS.append(formatColor(.4,0.13,0.91)) # Purple + +TEAM_COLORS = GHOST_COLORS[:2] + +GHOST_SHAPE = [ + ( 0, 0.3 ), + ( 0.25, 0.75 ), + ( 0.5, 0.3 ), + ( 0.75, 0.75 ), + ( 0.75, -0.5 ), + ( 0.5, -0.75 ), + (-0.5, -0.75 ), + (-0.75, -0.5 ), + (-0.75, 0.75 ), + (-0.5, 0.3 ), + (-0.25, 0.75 ) + ] +GHOST_SIZE = 0.65 +SCARED_COLOR = formatColor(1,1,1) + +GHOST_VEC_COLORS = [colorToVector(c) for c in GHOST_COLORS] + +PACMAN_COLOR = formatColor(255.0/255.0,255.0/255.0,61.0/255) +PACMAN_SCALE = 0.5 +#pacman_speed = 0.25 + +# Food +FOOD_COLOR = formatColor(1,1,1) +FOOD_SIZE = 0.1 + +# Laser +LASER_COLOR = formatColor(1,0,0) +LASER_SIZE = 0.02 + +# Capsule graphics +CAPSULE_COLOR = formatColor(1,1,1) +CAPSULE_SIZE = 0.25 + +# Drawing walls +WALL_RADIUS = 0.15 + +class InfoPane: + def __init__(self, layout, gridSize): + self.gridSize = gridSize + self.width = (layout.width) * gridSize + self.base = (layout.height + 1) * gridSize + self.height = INFO_PANE_HEIGHT + self.fontSize = 24 + self.textColor = PACMAN_COLOR + self.drawPane() + + def toScreen(self, pos, y = None): + """ + Translates a point relative from the bottom left of the info pane. + """ + if y == None: + x,y = pos + else: + x = pos + + x = self.gridSize + x # Margin + y = self.base + y + return x,y + + def drawPane(self): + self.scoreText = text( self.toScreen(0, 0 ), self.textColor, "SCORE: 0", "Times", self.fontSize, "bold") + + def initializeGhostDistances(self, distances): + self.ghostDistanceText = [] + + size = 20 + if self.width < 240: + size = 12 + if self.width < 160: + size = 10 + + for i, d in enumerate(distances): + t = text( self.toScreen(self.width//2 + self.width//8 * i, 0), GHOST_COLORS[i+1], d, "Times", size, "bold") + self.ghostDistanceText.append(t) + + def updateScore(self, score): + changeText(self.scoreText, "SCORE: % 4d" % score) + + def setTeam(self, isBlue): + text = "RED TEAM" + if isBlue: text = "BLUE TEAM" + self.teamText = text( self.toScreen(300, 0 ), self.textColor, text, "Times", self.fontSize, "bold") + + def updateGhostDistances(self, distances): + if len(distances) == 0: return + if 'ghostDistanceText' not in dir(self): self.initializeGhostDistances(distances) + else: + for i, d in enumerate(distances): + changeText(self.ghostDistanceText[i], d) + + def drawGhost(self): + pass + + def drawPacman(self): + pass + + def drawWarning(self): + pass + + def clearIcon(self): + pass + + def updateMessage(self, message): + pass + + def clearMessage(self): + pass + + +class PacmanGraphics: + def __init__(self, zoom=1.0, frameTime=0.0, capture=False): + self.have_window = 0 + self.currentGhostImages = {} + self.pacmanImage = None + self.zoom = zoom + self.gridSize = DEFAULT_GRID_SIZE * zoom + self.capture = capture + self.frameTime = frameTime + + def checkNullDisplay(self): + return False + + def initialize(self, state, isBlue = False): + self.isBlue = isBlue + self.startGraphics(state) + + # self.drawDistributions(state) + self.distributionImages = None # Initialized lazily + self.drawStaticObjects(state) + self.drawAgentObjects(state) + + # Information + self.previousState = state + + def startGraphics(self, state): + self.layout = state.layout + layout = self.layout + self.width = layout.width + self.height = layout.height + self.make_window(self.width, self.height) + self.infoPane = InfoPane(layout, self.gridSize) + self.currentState = layout + + def drawDistributions(self, state): + walls = state.layout.walls + dist = [] + for x in range(walls.width): + distx = [] + dist.append(distx) + for y in range(walls.height): + ( screen_x, screen_y ) = self.to_screen( (x, y) ) + block = square( (screen_x, screen_y), + 0.5 * self.gridSize, + color = BACKGROUND_COLOR, + filled = 1, behind=2) + distx.append(block) + self.distributionImages = dist + + def drawStaticObjects(self, state): + layout = self.layout + self.drawWalls(layout.walls) + self.food = self.drawFood(layout.food) + self.capsules = self.drawCapsules(layout.capsules) + refresh() + + def drawAgentObjects(self, state): + self.agentImages = [] # (agentState, image) + for index, agent in enumerate(state.agentStates): + if agent.isPacman: + image = self.drawPacman(agent, index) + self.agentImages.append( (agent, image) ) + else: + image = self.drawGhost(agent, index) + self.agentImages.append( (agent, image) ) + refresh() + + def swapImages(self, agentIndex, newState): + """ + Changes an image from a ghost to a pacman or vis versa (for capture) + """ + prevState, prevImage = self.agentImages[agentIndex] + for item in prevImage: remove_from_screen(item) + if newState.isPacman: + image = self.drawPacman(newState, agentIndex) + self.agentImages[agentIndex] = (newState, image ) + else: + image = self.drawGhost(newState, agentIndex) + self.agentImages[agentIndex] = (newState, image ) + refresh() + + def update(self, newState): + agentIndex = newState._agentMoved + agentState = newState.agentStates[agentIndex] + + if self.agentImages[agentIndex][0].isPacman != agentState.isPacman: self.swapImages(agentIndex, agentState) + prevState, prevImage = self.agentImages[agentIndex] + if agentState.isPacman: + self.animatePacman(agentState, prevState, prevImage) + else: + self.moveGhost(agentState, agentIndex, prevState, prevImage) + self.agentImages[agentIndex] = (agentState, prevImage) + + if newState._foodEaten != None: + self.removeFood(newState._foodEaten, self.food) + if newState._capsuleEaten != None: + self.removeCapsule(newState._capsuleEaten, self.capsules) + self.infoPane.updateScore(newState.score) + if 'ghostDistances' in dir(newState): + self.infoPane.updateGhostDistances(newState.ghostDistances) + + def make_window(self, width, height): + grid_width = (width-1) * self.gridSize + grid_height = (height-1) * self.gridSize + screen_width = 2*self.gridSize + grid_width + screen_height = 2*self.gridSize + grid_height + INFO_PANE_HEIGHT + + begin_graphics(screen_width, + screen_height, + BACKGROUND_COLOR, + "CS188 Pacman") + + def drawPacman(self, pacman, index): + position = self.getPosition(pacman) + screen_point = self.to_screen(position) + endpoints = self.getEndpoints(self.getDirection(pacman)) + + width = PACMAN_OUTLINE_WIDTH + outlineColor = PACMAN_COLOR + fillColor = PACMAN_COLOR + + if self.capture: + outlineColor = TEAM_COLORS[index % 2] + fillColor = GHOST_COLORS[index] + width = PACMAN_CAPTURE_OUTLINE_WIDTH + + return [circle(screen_point, PACMAN_SCALE * self.gridSize, + fillColor = fillColor, outlineColor = outlineColor, + endpoints = endpoints, + width = width)] + + def getEndpoints(self, direction, position=(0,0)): + x, y = position + pos = x - int(x) + y - int(y) + width = 30 + 80 * math.sin(math.pi* pos) + + delta = width / 2 + if (direction == 'West'): + endpoints = (180+delta, 180-delta) + elif (direction == 'North'): + endpoints = (90+delta, 90-delta) + elif (direction == 'South'): + endpoints = (270+delta, 270-delta) + else: + endpoints = (0+delta, 0-delta) + return endpoints + + def movePacman(self, position, direction, image): + screenPosition = self.to_screen(position) + endpoints = self.getEndpoints( direction, position ) + r = PACMAN_SCALE * self.gridSize + moveCircle(image[0], screenPosition, r, endpoints) + refresh() + + def animatePacman(self, pacman, prevPacman, image): + if self.frameTime < 0: + print('Press any key to step forward, "q" to play') + keys = wait_for_keys() + if 'q' in keys: + self.frameTime = 0.1 + if self.frameTime > 0.01 or self.frameTime < 0: + start = time.time() + fx, fy = self.getPosition(prevPacman) + px, py = self.getPosition(pacman) + frames = 4.0 + for i in range(1,int(frames) + 1): + pos = px*i/frames + fx*(frames-i)/frames, py*i/frames + fy*(frames-i)/frames + self.movePacman(pos, self.getDirection(pacman), image) + refresh() + sleep(abs(self.frameTime) / frames) + else: + self.movePacman(self.getPosition(pacman), self.getDirection(pacman), image) + refresh() + + def getGhostColor(self, ghost, ghostIndex): + if ghost.scaredTimer > 0: + return SCARED_COLOR + else: + return GHOST_COLORS[ghostIndex] + + def drawGhost(self, ghost, agentIndex): + pos = self.getPosition(ghost) + dir = self.getDirection(ghost) + (screen_x, screen_y) = (self.to_screen(pos) ) + coords = [] + for (x, y) in GHOST_SHAPE: + coords.append((x*self.gridSize*GHOST_SIZE + screen_x, y*self.gridSize*GHOST_SIZE + screen_y)) + + colour = self.getGhostColor(ghost, agentIndex) + body = polygon(coords, colour, filled = 1) + WHITE = formatColor(1.0, 1.0, 1.0) + BLACK = formatColor(0.0, 0.0, 0.0) + + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + leftEye = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + rightEye = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + leftPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + rightPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + ghostImageParts = [] + ghostImageParts.append(body) + ghostImageParts.append(leftEye) + ghostImageParts.append(rightEye) + ghostImageParts.append(leftPupil) + ghostImageParts.append(rightPupil) + + return ghostImageParts + + def moveEyes(self, pos, dir, eyes): + (screen_x, screen_y) = (self.to_screen(pos) ) + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + moveCircle(eyes[0],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[1],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[2],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + moveCircle(eyes[3],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + + def moveGhost(self, ghost, ghostIndex, prevGhost, ghostImageParts): + old_x, old_y = self.to_screen(self.getPosition(prevGhost)) + new_x, new_y = self.to_screen(self.getPosition(ghost)) + delta = new_x - old_x, new_y - old_y + + for ghostImagePart in ghostImageParts: + move_by(ghostImagePart, delta) + refresh() + + if ghost.scaredTimer > 0: + color = SCARED_COLOR + else: + color = GHOST_COLORS[ghostIndex] + edit(ghostImageParts[0], ('fill', color), ('outline', color)) + self.moveEyes(self.getPosition(ghost), self.getDirection(ghost), ghostImageParts[-4:]) + refresh() + + def getPosition(self, agentState): + if agentState.configuration == None: return (-1000, -1000) + return agentState.getPosition() + + def getDirection(self, agentState): + if agentState.configuration == None: return Directions.STOP + return agentState.configuration.getDirection() + + def finish(self): + end_graphics() + + def to_screen(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + # Fixes some TK issue with off-center circles + def to_screen2(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + def drawWalls(self, wallMatrix): + wallColor = WALL_COLOR + for xNum, x in enumerate(wallMatrix): + if self.capture and (xNum * 2) < wallMatrix.width: wallColor = TEAM_COLORS[0] + if self.capture and (xNum * 2) >= wallMatrix.width: wallColor = TEAM_COLORS[1] + + for yNum, cell in enumerate(x): + if cell: # There's a wall here + pos = (xNum, yNum) + screen = self.to_screen(pos) + screen2 = self.to_screen2(pos) + + # draw each quadrant of the square based on adjacent walls + wIsWall = self.isWall(xNum-1, yNum, wallMatrix) + eIsWall = self.isWall(xNum+1, yNum, wallMatrix) + nIsWall = self.isWall(xNum, yNum+1, wallMatrix) + sIsWall = self.isWall(xNum, yNum-1, wallMatrix) + nwIsWall = self.isWall(xNum-1, yNum+1, wallMatrix) + swIsWall = self.isWall(xNum-1, yNum-1, wallMatrix) + neIsWall = self.isWall(xNum+1, yNum+1, wallMatrix) + seIsWall = self.isWall(xNum+1, yNum-1, wallMatrix) + + # NE quadrant + if (not nIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (0,91), 'arc') + if (nIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (eIsWall) and (not neIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (180,271), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # NW quadrant + if (not nIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (90,181), 'arc') + if (nIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (wIsWall) and (not nwIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (270,361), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # SE quadrant + if (not sIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (270,361), 'arc') + if (sIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (eIsWall) and (not seIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (90,181), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5, self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + # SW quadrant + if (not sIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (180,271), 'arc') + if (sIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (wIsWall) and (not swIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (0,91), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + def isWall(self, x, y, walls): + if x < 0 or y < 0: + return False + if x >= walls.width or y >= walls.height: + return False + return walls[x][y] + + def drawFood(self, foodMatrix ): + foodImages = [] + color = FOOD_COLOR + for xNum, x in enumerate(foodMatrix): + if self.capture and (xNum * 2) <= foodMatrix.width: color = TEAM_COLORS[0] + if self.capture and (xNum * 2) > foodMatrix.width: color = TEAM_COLORS[1] + imageRow = [] + foodImages.append(imageRow) + for yNum, cell in enumerate(x): + if cell: # There's food here + screen = self.to_screen((xNum, yNum )) + dot = circle( screen, + FOOD_SIZE * self.gridSize, + outlineColor = color, fillColor = color, + width = 1) + imageRow.append(dot) + else: + imageRow.append(None) + return foodImages + + def drawCapsules(self, capsules ): + capsuleImages = {} + for capsule in capsules: + ( screen_x, screen_y ) = self.to_screen(capsule) + dot = circle( (screen_x, screen_y), + CAPSULE_SIZE * self.gridSize, + outlineColor = CAPSULE_COLOR, + fillColor = CAPSULE_COLOR, + width = 1) + capsuleImages[capsule] = dot + return capsuleImages + + def removeFood(self, cell, foodImages ): + x, y = cell + remove_from_screen(foodImages[x][y]) + + def removeCapsule(self, cell, capsuleImages ): + x, y = cell + remove_from_screen(capsuleImages[(x, y)]) + + def drawExpandedCells(self, cells): + """ + Draws an overlay of expanded grid positions for search agents + """ + n = float(len(cells)) + baseColor = [1.0, 0.0, 0.0] + self.clearExpandedCells() + self.expandedCells = [] + for k, cell in enumerate(cells): + screenPos = self.to_screen( cell) + cellColor = formatColor(*[(n-k) * c * .5 / n + .25 for c in baseColor]) + block = square(screenPos, + 0.5 * self.gridSize, + color = cellColor, + filled = 1, behind=2) + self.expandedCells.append(block) + if self.frameTime < 0: + refresh() + + def clearExpandedCells(self): + if 'expandedCells' in dir(self) and len(self.expandedCells) > 0: + for cell in self.expandedCells: + remove_from_screen(cell) + + + def updateDistributions(self, distributions): + "Draws an agent's belief distributions" + # copy all distributions so we don't change their state + distributions = map(lambda x: x.copy(), distributions) + if self.distributionImages == None: + self.drawDistributions(self.previousState) + for x in range(len(self.distributionImages)): + for y in range(len(self.distributionImages[0])): + image = self.distributionImages[x][y] + weights = [dist[ (x,y) ] for dist in distributions] + + if sum(weights) != 0: + pass + # Fog of war + color = [0.0,0.0,0.0] + colors = GHOST_VEC_COLORS[1:] # With Pacman + if self.capture: colors = GHOST_VEC_COLORS + for weight, gcolor in zip(weights, colors): + color = [min(1.0, c + 0.95 * g * weight ** .3) for c,g in zip(color, gcolor)] + changeColor(image, formatColor(*color)) + refresh() + +class FirstPersonPacmanGraphics(PacmanGraphics): + def __init__(self, zoom = 1.0, showGhosts = True, capture = False, frameTime=0): + PacmanGraphics.__init__(self, zoom, frameTime=frameTime) + self.showGhosts = showGhosts + self.capture = capture + + def initialize(self, state, isBlue = False): + + self.isBlue = isBlue + PacmanGraphics.startGraphics(self, state) + # Initialize distribution images + walls = state.layout.walls + dist = [] + self.layout = state.layout + + # Draw the rest + self.distributionImages = None # initialize lazily + self.drawStaticObjects(state) + self.drawAgentObjects(state) + + # Information + self.previousState = state + + def lookAhead(self, config, state): + if config.getDirection() == 'Stop': + return + else: + pass + # Draw relevant ghosts + allGhosts = state.getGhostStates() + visibleGhosts = state.getVisibleGhosts() + for i, ghost in enumerate(allGhosts): + if ghost in visibleGhosts: + self.drawGhost(ghost, i) + else: + self.currentGhostImages[i] = None + + def getGhostColor(self, ghost, ghostIndex): + return GHOST_COLORS[ghostIndex] + + def getPosition(self, ghostState): + if not self.showGhosts and not ghostState.isPacman and ghostState.getPosition()[1] > 1: + return (-1000, -1000) + else: + return PacmanGraphics.getPosition(self, ghostState) + +def add(x, y): + return (x[0] + y[0], x[1] + y[1]) + + +# Saving graphical output +# ----------------------- +# Note: to make an animated gif from this postscript output, try the command: +# convert -delay 7 -loop 1 -compress lzw -layers optimize frame* out.gif +# convert is part of imagemagick (freeware) + +SAVE_POSTSCRIPT = False +POSTSCRIPT_OUTPUT_DIR = 'frames' +FRAME_NUMBER = 0 +import os + +def saveFrame(): + "Saves the current graphical output as a postscript file" + global SAVE_POSTSCRIPT, FRAME_NUMBER, POSTSCRIPT_OUTPUT_DIR + if not SAVE_POSTSCRIPT: return + if not os.path.exists(POSTSCRIPT_OUTPUT_DIR): os.mkdir(POSTSCRIPT_OUTPUT_DIR) + name = os.path.join(POSTSCRIPT_OUTPUT_DIR, 'frame_%08d.ps' % FRAME_NUMBER) + FRAME_NUMBER += 1 + writePostscript(name) # writes the current canvas diff --git a/proj1-search-python3/graphicsUtils.py b/proj1-search-python3/graphicsUtils.py new file mode 100644 index 0000000..22e6ae1 --- /dev/null +++ b/proj1-search-python3/graphicsUtils.py @@ -0,0 +1,402 @@ +# graphicsUtils.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import math +import random +import string +import time +import types +import tkinter +import os.path + +_Windows = sys.platform == 'win32' # True if on Win95/98/NT + +_root_window = None # The root window for graphics output +_canvas = None # The canvas which holds graphics +_canvas_xs = None # Size of canvas object +_canvas_ys = None +_canvas_x = None # Current position on canvas +_canvas_y = None +_canvas_col = None # Current colour (set to black below) +_canvas_tsize = 12 +_canvas_tserifs = 0 + +def formatColor(r, g, b): + return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)) + +def colorToVector(color): + return list(map(lambda x: int(x, 16) / 256.0, [color[1:3], color[3:5], color[5:7]])) + +if _Windows: + _canvas_tfonts = ['times new roman', 'lucida console'] +else: + _canvas_tfonts = ['times', 'lucidasans-24'] + pass # XXX need defaults here + +def sleep(secs): + global _root_window + if _root_window == None: + time.sleep(secs) + else: + _root_window.update_idletasks() + _root_window.after(int(1000 * secs), _root_window.quit) + _root_window.mainloop() + +def begin_graphics(width=640, height=480, color=formatColor(0, 0, 0), title=None): + + global _root_window, _canvas, _canvas_x, _canvas_y, _canvas_xs, _canvas_ys, _bg_color + + # Check for duplicate call + if _root_window is not None: + # Lose the window. + _root_window.destroy() + + # Save the canvas size parameters + _canvas_xs, _canvas_ys = width - 1, height - 1 + _canvas_x, _canvas_y = 0, _canvas_ys + _bg_color = color + + # Create the root window + _root_window = tkinter.Tk() + _root_window.protocol('WM_DELETE_WINDOW', _destroy_window) + _root_window.title(title or 'Graphics Window') + _root_window.resizable(0, 0) + + # Create the canvas object + try: + _canvas = tkinter.Canvas(_root_window, width=width, height=height) + _canvas.pack() + draw_background() + _canvas.update() + except: + _root_window = None + raise + + # Bind to key-down and key-up events + _root_window.bind( "", _keypress ) + _root_window.bind( "", _keyrelease ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _leftclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _ctrl_leftclick) + _clear_keys() + +_leftclick_loc = None +_rightclick_loc = None +_ctrl_leftclick_loc = None + +def _leftclick(event): + global _leftclick_loc + _leftclick_loc = (event.x, event.y) + +def _rightclick(event): + global _rightclick_loc + _rightclick_loc = (event.x, event.y) + +def _ctrl_leftclick(event): + global _ctrl_leftclick_loc + _ctrl_leftclick_loc = (event.x, event.y) + +def wait_for_click(): + while True: + global _leftclick_loc + global _rightclick_loc + global _ctrl_leftclick_loc + if _leftclick_loc != None: + val = _leftclick_loc + _leftclick_loc = None + return val, 'left' + if _rightclick_loc != None: + val = _rightclick_loc + _rightclick_loc = None + return val, 'right' + if _ctrl_leftclick_loc != None: + val = _ctrl_leftclick_loc + _ctrl_leftclick_loc = None + return val, 'ctrl_left' + sleep(0.05) + +def draw_background(): + corners = [(0,0), (0, _canvas_ys), (_canvas_xs, _canvas_ys), (_canvas_xs, 0)] + polygon(corners, _bg_color, fillColor=_bg_color, filled=True, smoothed=False) + +def _destroy_window(event=None): + sys.exit(0) +# global _root_window +# _root_window.destroy() +# _root_window = None + #print("DESTROY") + +def end_graphics(): + global _root_window, _canvas, _mouse_enabled + try: + try: + sleep(1) + if _root_window != None: + _root_window.destroy() + except SystemExit as e: + print('Ending graphics raised an exception:', e) + finally: + _root_window = None + _canvas = None + _mouse_enabled = 0 + _clear_keys() + +def clear_screen(background=None): + global _canvas_x, _canvas_y + _canvas.delete('all') + draw_background() + _canvas_x, _canvas_y = 0, _canvas_ys + +def polygon(coords, outlineColor, fillColor=None, filled=1, smoothed=1, behind=0, width=1): + c = [] + for coord in coords: + c.append(coord[0]) + c.append(coord[1]) + if fillColor == None: fillColor = outlineColor + if filled == 0: fillColor = "" + poly = _canvas.create_polygon(c, outline=outlineColor, fill=fillColor, smooth=smoothed, width=width) + if behind > 0: + _canvas.tag_lower(poly, behind) # Higher should be more visible + return poly + +def square(pos, r, color, filled=1, behind=0): + x, y = pos + coords = [(x - r, y - r), (x + r, y - r), (x + r, y + r), (x - r, y + r)] + return polygon(coords, color, color, filled, 0, behind=behind) + +def circle(pos, r, outlineColor, fillColor=None, endpoints=None, style='pieslice', width=2): + x, y = pos + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + return _canvas.create_arc(x0, y0, x1, y1, outline=outlineColor, fill=fillColor or outlineColor, + extent=e[1] - e[0], start=e[0], style=style, width=width) + +def image(pos, file="../../blueghost.gif"): + x, y = pos + # img = PhotoImage(file=file) + return _canvas.create_image(x, y, image = tkinter.PhotoImage(file=file), anchor = tkinter.NW) + + +def refresh(): + _canvas.update_idletasks() + +def moveCircle(id, pos, r, endpoints=None): + global _canvas_x, _canvas_y + + x, y = pos +# x0, x1 = x - r, x + r + 1 +# y0, y1 = y - r, y + r + 1 + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + if os.path.isfile('flag'): + edit(id, ('extent', e[1] - e[0])) + else: + edit(id, ('start', e[0]), ('extent', e[1] - e[0])) + move_to(id, x0, y0) + +def edit(id, *args): + _canvas.itemconfigure(id, **dict(args)) + +def text(pos, color, contents, font='Helvetica', size=12, style='normal', anchor="nw"): + global _canvas_x, _canvas_y + x, y = pos + font = (font, str(size), style) + return _canvas.create_text(x, y, fill=color, text=contents, font=font, anchor=anchor) + +def changeText(id, newText, font=None, size=12, style='normal'): + _canvas.itemconfigure(id, text=newText) + if font != None: + _canvas.itemconfigure(id, font=(font, '-%d' % size, style)) + +def changeColor(id, newColor): + _canvas.itemconfigure(id, fill=newColor) + +def line(here, there, color=formatColor(0, 0, 0), width=2): + x0, y0 = here[0], here[1] + x1, y1 = there[0], there[1] + return _canvas.create_line(x0, y0, x1, y1, fill=color, width=width) + +############################################################################## +### Keypress handling ######################################################## +############################################################################## + +# We bind to key-down and key-up events. + +_keysdown = {} +_keyswaiting = {} +# This holds an unprocessed key release. We delay key releases by up to +# one call to keys_pressed() to get round a problem with auto repeat. +_got_release = None + +def _keypress(event): + global _got_release + #remap_arrows(event) + _keysdown[event.keysym] = 1 + _keyswaiting[event.keysym] = 1 +# print(event.char, event.keycode) + _got_release = None + +def _keyrelease(event): + global _got_release + #remap_arrows(event) + try: + del _keysdown[event.keysym] + except: + pass + _got_release = 1 + +def remap_arrows(event): + # TURN ARROW PRESSES INTO LETTERS (SHOULD BE IN KEYBOARD AGENT) + if event.char in ['a', 's', 'd', 'w']: + return + if event.keycode in [37, 101]: # LEFT ARROW (win / x) + event.char = 'a' + if event.keycode in [38, 99]: # UP ARROW + event.char = 'w' + if event.keycode in [39, 102]: # RIGHT ARROW + event.char = 'd' + if event.keycode in [40, 104]: # DOWN ARROW + event.char = 's' + +def _clear_keys(event=None): + global _keysdown, _got_release, _keyswaiting + _keysdown = {} + _keyswaiting = {} + _got_release = None + +def keys_pressed(d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + d_o_e(d_w) + if _got_release: + d_o_e(d_w) + return _keysdown.keys() + +def keys_waiting(): + global _keyswaiting + keys = _keyswaiting.keys() + _keyswaiting = {} + return keys + +# Block for a list of keys... + +def wait_for_keys(): + keys = [] + while keys == []: + keys = keys_pressed() + sleep(0.05) + return keys + +def remove_from_screen(x, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + _canvas.delete(x) + d_o_e(d_w) + +def _adjust_coords(coord_list, x, y): + for i in range(0, len(coord_list), 2): + coord_list[i] = coord_list[i] + x + coord_list[i + 1] = coord_list[i + 1] + y + return coord_list + +def move_to(object, x, y=None, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + if y is None: + try: x, y = x + except: raise 'incomprehensible coordinates' + + horiz = True + newCoords = [] + current_x, current_y = _canvas.coords(object)[0:2] # first point + for coord in _canvas.coords(object): + if horiz: + inc = x - current_x + else: + inc = y - current_y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + +def move_by(object, x, y=None, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT, lift=False): + if y is None: + try: x, y = x + except: raise Exception('incomprehensible coordinates') + + horiz = True + newCoords = [] + for coord in _canvas.coords(object): + if horiz: + inc = x + else: + inc = y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + if lift: + _canvas.tag_raise(object) + +def writePostscript(filename): + "Writes the current canvas to a postscript file." + psfile = open(filename, 'w') + psfile.write(_canvas.postscript(pageanchor='sw', + y='0.c', + x='0.c')) + psfile.close() + +ghost_shape = [ + (0, - 0.5), + (0.25, - 0.75), + (0.5, - 0.5), + (0.75, - 0.75), + (0.75, 0.5), + (0.5, 0.75), + (- 0.5, 0.75), + (- 0.75, 0.5), + (- 0.75, - 0.75), + (- 0.5, - 0.5), + (- 0.25, - 0.75) + ] + +if __name__ == '__main__': + begin_graphics() + clear_screen() + ghost_shape = [(x * 10 + 20, y * 10 + 20) for x, y in ghost_shape] + g = polygon(ghost_shape, formatColor(1, 1, 1)) + move_to(g, (50, 50)) + circle((150, 150), 20, formatColor(0.7, 0.3, 0.0), endpoints=[15, - 15]) + sleep(2) diff --git a/proj1-search-python3/keyboardAgents.py b/proj1-search-python3/keyboardAgents.py new file mode 100644 index 0000000..624021d --- /dev/null +++ b/proj1-search-python3/keyboardAgents.py @@ -0,0 +1,84 @@ +# keyboardAgents.py +# ----------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Directions +import random + +class KeyboardAgent(Agent): + """ + An agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'a' + EAST_KEY = 'd' + NORTH_KEY = 'w' + SOUTH_KEY = 's' + STOP_KEY = 'q' + + def __init__( self, index = 0 ): + + self.lastMove = Directions.STOP + self.index = index + self.keys = [] + + def getAction( self, state): + from graphicsUtils import keys_waiting + from graphicsUtils import keys_pressed + keys = list(keys_waiting()) + list(keys_pressed()) + if keys != []: + self.keys = keys + + legal = state.getLegalActions(self.index) + move = self.getMove(legal) + + if move == Directions.STOP: + # Try to move in the same direction as before + if self.lastMove in legal: + move = self.lastMove + + if (self.STOP_KEY in self.keys) and Directions.STOP in legal: move = Directions.STOP + + if move not in legal: + move = random.choice(legal) + + self.lastMove = move + return move + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys or 'Left' in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys or 'Right' in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys or 'Up' in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys or 'Down' in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move + +class KeyboardAgent2(KeyboardAgent): + """ + A second agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'j' + EAST_KEY = "l" + NORTH_KEY = 'i' + SOUTH_KEY = 'k' + STOP_KEY = 'u' + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move diff --git a/proj1-search-python3/layout.py b/proj1-search-python3/layout.py new file mode 100644 index 0000000..ebba32a --- /dev/null +++ b/proj1-search-python3/layout.py @@ -0,0 +1,150 @@ +# layout.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from util import manhattanDistance +from game import Grid +import os +import random +from functools import reduce + +VISIBILITY_MATRIX_CACHE = {} + +class Layout: + """ + A Layout manages the static information about the game board. + """ + + def __init__(self, layoutText): + self.width = len(layoutText[0]) + self.height= len(layoutText) + self.walls = Grid(self.width, self.height, False) + self.food = Grid(self.width, self.height, False) + self.capsules = [] + self.agentPositions = [] + self.numGhosts = 0 + self.processLayoutText(layoutText) + self.layoutText = layoutText + self.totalFood = len(self.food.asList()) + # self.initializeVisibilityMatrix() + + def getNumGhosts(self): + return self.numGhosts + + def initializeVisibilityMatrix(self): + global VISIBILITY_MATRIX_CACHE + if reduce(str.__add__, self.layoutText) not in VISIBILITY_MATRIX_CACHE: + from game import Directions + vecs = [(-0.5,0), (0.5,0),(0,-0.5),(0,0.5)] + dirs = [Directions.NORTH, Directions.SOUTH, Directions.WEST, Directions.EAST] + vis = Grid(self.width, self.height, {Directions.NORTH:set(), Directions.SOUTH:set(), Directions.EAST:set(), Directions.WEST:set(), Directions.STOP:set()}) + for x in range(self.width): + for y in range(self.height): + if self.walls[x][y] == False: + for vec, direction in zip(vecs, dirs): + dx, dy = vec + nextx, nexty = x + dx, y + dy + while (nextx + nexty) != int(nextx) + int(nexty) or not self.walls[int(nextx)][int(nexty)] : + vis[x][y][direction].add((nextx, nexty)) + nextx, nexty = x + dx, y + dy + self.visibility = vis + VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] = vis + else: + self.visibility = VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] + + def isWall(self, pos): + x, col = pos + return self.walls[x][col] + + def getRandomLegalPosition(self): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + while self.isWall( (x, y) ): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + return (x,y) + + def getRandomCorner(self): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + return random.choice(poses) + + def getFurthestCorner(self, pacPos): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + dist, pos = max([(manhattanDistance(p, pacPos), p) for p in poses]) + return pos + + def isVisibleFrom(self, ghostPos, pacPos, pacDirection): + row, col = [int(x) for x in pacPos] + return ghostPos in self.visibility[row][col][pacDirection] + + def __str__(self): + return "\n".join(self.layoutText) + + def deepCopy(self): + return Layout(self.layoutText[:]) + + def processLayoutText(self, layoutText): + """ + Coordinates are flipped from the input format to the (x,y) convention here + + The shape of the maze. Each character + represents a different type of object. + % - Wall + . - Food + o - Capsule + G - Ghost + P - Pacman + Other characters are ignored. + """ + maxY = self.height - 1 + for y in range(self.height): + for x in range(self.width): + layoutChar = layoutText[maxY - y][x] + self.processLayoutChar(x, y, layoutChar) + self.agentPositions.sort() + self.agentPositions = [ ( i == 0, pos) for i, pos in self.agentPositions] + + def processLayoutChar(self, x, y, layoutChar): + if layoutChar == '%': + self.walls[x][y] = True + elif layoutChar == '.': + self.food[x][y] = True + elif layoutChar == 'o': + self.capsules.append((x, y)) + elif layoutChar == 'P': + self.agentPositions.append( (0, (x, y) ) ) + elif layoutChar in ['G']: + self.agentPositions.append( (1, (x, y) ) ) + self.numGhosts += 1 + elif layoutChar in ['1', '2', '3', '4']: + self.agentPositions.append( (int(layoutChar), (x,y))) + self.numGhosts += 1 +def getLayout(name, back = 2): + if name.endswith('.lay'): + layout = tryToLoad('layouts/' + name) + if layout == None: layout = tryToLoad(name) + else: + layout = tryToLoad('layouts/' + name + '.lay') + if layout == None: layout = tryToLoad(name + '.lay') + if layout == None and back >= 0: + curdir = os.path.abspath('.') + os.chdir('..') + layout = getLayout(name, back -1) + os.chdir(curdir) + return layout + +def tryToLoad(fullname): + if(not os.path.exists(fullname)): return None + f = open(fullname) + try: return Layout([line.strip() for line in f]) + finally: f.close() diff --git a/proj1-search-python3/layouts/bigCorners.lay b/proj1-search-python3/layouts/bigCorners.lay new file mode 100644 index 0000000..4d89d7b --- /dev/null +++ b/proj1-search-python3/layouts/bigCorners.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % %.% +% %%%%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % +% % % % % % %%% %%% %%% % % % % % % +% % % % % %% % % % % % % % % % +% % %%%%% % %%% %%% % %%% %%% %%%%% +% % % % % % % % % % % +% %%% % % % %%% %%% %%%%%%%%% % %%% +% % % % % % % +% %%% %%%%%%%%%%%%%%%%%%%%% % % %%% % +% % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % %P % % % % % % +% %%% %%% %%% % %%% % % %%%%% % %%%%% +% % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % +% % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/bigMaze.lay b/proj1-search-python3/layouts/bigMaze.lay new file mode 100644 index 0000000..e11fade --- /dev/null +++ b/proj1-search-python3/layouts/bigMaze.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % % % % % % % +% %%%%%%% % %%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % % % % +% % % % % %%% %%% %%% %%% % % % % % % +% % % % % % % % % +%%% %%%%%%% % % %%%%% %%% % %%% %%%%% +% % % % % % % % % % +%%%%% % % %%%%%%%%% %%%%%%%%%%% % %%% +% % % % % % % % % +% %%% %%%%% %%%%%%%%% %%%%% % % %%% % +% % % % % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % % % % % % % % +% %%% %%% %%%%% %%% % % %%%%% % %%%%% +% % % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % % % +% % % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % % % % P% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/bigSafeSearch.lay b/proj1-search-python3/layouts/bigSafeSearch.lay new file mode 100644 index 0000000..b5fd414 --- /dev/null +++ b/proj1-search-python3/layouts/bigSafeSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.%.........%% G % o%%%%.....% +%.%.%%%%%%%.%%%%%% %%%%%%%.%%.% +%............%...%............% +%%%%%...%%%.. ..%.%...%.%%% +%o%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +% ..........Po...%...%. o% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/bigSearch.lay b/proj1-search-python3/layouts/bigSearch.lay new file mode 100644 index 0000000..bb59eb8 --- /dev/null +++ b/proj1-search-python3/layouts/bigSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.....%.................%.....% +%.%%%.%.%%%.%%%%%%%.%%%.%.....% +%.%...%.%......%......%.%.....% +%...%%%.%.%%%%.%.%%%%...%%%...% +%%%.%.%.%.%......%..%.%...%.%%% +%...%.%%%.%.%%% %%%.%.%%%.%...% +%.%%%.......% %.......%%%.% +%...%.%%%%%.%%%%%%%.%.%%%.%...% +%%%.%...%.%....%....%.%...%.%%% +%...%%%.%.%%%%.%.%%%%.%.%%%...% +%.......%......%......%.....%.% +%.....%.%%%.%%%%%%%.%%%.%.%%%.% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/boxSearch.lay b/proj1-search-python3/layouts/boxSearch.lay new file mode 100644 index 0000000..4a113fc --- /dev/null +++ b/proj1-search-python3/layouts/boxSearch.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%% +%. . . . . % % +% % % +%. . . . . %G% +% % % +%. . . . . % % +% % % +%. . . . . % % +% P %G% +%. . . . . % % +% % % +%. . . . . % % +% % % +%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/capsuleClassic.lay b/proj1-search-python3/layouts/capsuleClassic.lay new file mode 100644 index 0000000..06a5c51 --- /dev/null +++ b/proj1-search-python3/layouts/capsuleClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%% +%G. G ....% +%.% % %%%%%% %.%%.% +%.%o% % o% %.o%.% +%.%%%.% %%% %..%.% +%..... P %..%G% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/contestClassic.lay b/proj1-search-python3/layouts/contestClassic.lay new file mode 100644 index 0000000..84c8733 --- /dev/null +++ b/proj1-search-python3/layouts/contestClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%...... G GG%......% +%.%.%%.%% %%%.%%.%.% +%.%....% ooo%.%..%.% +%.%.%%.% %% %.%.%%.% +%o%......P....%....% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/contoursMaze.lay b/proj1-search-python3/layouts/contoursMaze.lay new file mode 100644 index 0000000..a068956 --- /dev/null +++ b/proj1-search-python3/layouts/contoursMaze.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% % +% P % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/greedySearch.lay b/proj1-search-python3/layouts/greedySearch.lay new file mode 100644 index 0000000..4072363 --- /dev/null +++ b/proj1-search-python3/layouts/greedySearch.lay @@ -0,0 +1,8 @@ +%%%%%% +%....% +% %%.% +% %%.% +%.P .% +%.%%%% +%....% +%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/mediumClassic.lay b/proj1-search-python3/layouts/mediumClassic.lay new file mode 100644 index 0000000..33c5db8 --- /dev/null +++ b/proj1-search-python3/layouts/mediumClassic.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%....% +%.%%.%.%%%%%%.%.%%.% +%.%..............%.% +%.%.%%.%% %%.%%.%.% +%......%G G%......% +%.%.%%.%%%%%%.%%.%.% +%.%..............%.% +%.%%.%.%%%%%%.%.%%.% +%....%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/mediumCorners.lay b/proj1-search-python3/layouts/mediumCorners.lay new file mode 100644 index 0000000..6a39756 --- /dev/null +++ b/proj1-search-python3/layouts/mediumCorners.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/mediumDottedMaze.lay b/proj1-search-python3/layouts/mediumDottedMaze.lay new file mode 100644 index 0000000..103f818 --- /dev/null +++ b/proj1-search-python3/layouts/mediumDottedMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %% ... % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %% %% %% ... % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% ... % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% ... % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % ... % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% ...... % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/mediumMaze.lay b/proj1-search-python3/layouts/mediumMaze.lay new file mode 100644 index 0000000..55c1236 --- /dev/null +++ b/proj1-search-python3/layouts/mediumMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/mediumSafeSearch.lay b/proj1-search-python3/layouts/mediumSafeSearch.lay new file mode 100644 index 0000000..e7d6b1c --- /dev/null +++ b/proj1-search-python3/layouts/mediumSafeSearch.lay @@ -0,0 +1,6 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.% ....%% G %%%%%% o%%.% +%.%o%%%%%%%.%%%%%%% %%%%%.% +% %%%.%%%%%.%%%%%%%.%%%.%.%%%.% +% ..........Po...%.........% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/mediumScaryMaze.lay b/proj1-search-python3/layouts/mediumScaryMaze.lay new file mode 100644 index 0000000..65d4c33 --- /dev/null +++ b/proj1-search-python3/layouts/mediumScaryMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %%GG % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %%GG %% % +% %% % % % % % %%%%% %%% %%%%%% % +% %% % % % % %% %%%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%% %% %%%%%%% %% %%%%%% % +%%%%%% % % %% %% % +% %%%%%% %% %% %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%% %%%%% %%%%%% % +%%%%%%%% % %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/mediumSearch.lay b/proj1-search-python3/layouts/mediumSearch.lay new file mode 100644 index 0000000..2f8af42 --- /dev/null +++ b/proj1-search-python3/layouts/mediumSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%%%%............% +%%%.%...%%%.........%.%...%.%%% +%...%%%.%.%%%%.%.%%%%%%.%%%...% +%.%.....%......%......%.....%.% +%.%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/minimaxClassic.lay b/proj1-search-python3/layouts/minimaxClassic.lay new file mode 100644 index 0000000..a547397 --- /dev/null +++ b/proj1-search-python3/layouts/minimaxClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%%% +%.P G% +% %.%G%%% +%G %%% +%%%%%%%%% diff --git a/proj1-search-python3/layouts/oddSearch.lay b/proj1-search-python3/layouts/oddSearch.lay new file mode 100644 index 0000000..2ddbc9a --- /dev/null +++ b/proj1-search-python3/layouts/oddSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%...%.........%%...% +%.%.%.%%%%%%%%%%.%.% +%..................% +%%%%%%%%.%.%%%%%%%P% +%%%%%%%%....... % +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/openClassic.lay b/proj1-search-python3/layouts/openClassic.lay new file mode 100644 index 0000000..6760b42 --- /dev/null +++ b/proj1-search-python3/layouts/openClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%%%%%%% +%.. P .... .... % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... G % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... o% +%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/openMaze.lay b/proj1-search-python3/layouts/openMaze.lay new file mode 100644 index 0000000..5dee689 --- /dev/null +++ b/proj1-search-python3/layouts/openMaze.lay @@ -0,0 +1,23 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% % % +% % % +% % % +% % % +% % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%% +% % % +% % % +% % % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/openSearch.lay b/proj1-search-python3/layouts/openSearch.lay new file mode 100644 index 0000000..f02d21d --- /dev/null +++ b/proj1-search-python3/layouts/openSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%..................% +%..................% +%........P.........% +%..................% +%..................% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/originalClassic.lay b/proj1-search-python3/layouts/originalClassic.lay new file mode 100644 index 0000000..b2770c5 --- /dev/null +++ b/proj1-search-python3/layouts/originalClassic.lay @@ -0,0 +1,27 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o%%%%.%%%%%.%%.%%%%%.%%%%o% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%..........................% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%......%%....%%....%%......% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%% %%%% %.%%%%%% +% . %G GG G% . % +%%%%%%.% %%%%%%%%%% %.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%%%%%%%% %.%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o..%%....... .......%%..o% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%......%%....%%....%%......% +%.%%%%%%%%%%.%%.%%%%%%%%%%.% +%.............P............% +%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/powerClassic.lay b/proj1-search-python3/layouts/powerClassic.lay new file mode 100644 index 0000000..3f3d983 --- /dev/null +++ b/proj1-search-python3/layouts/powerClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%o....o%GGGG%o....o% +%..%...%% %%...%..% +%.%o.%........%.o%.% +%.o%.%.%%%%%%.%.%o.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/smallClassic.lay b/proj1-search-python3/layouts/smallClassic.lay new file mode 100644 index 0000000..ce6c1d9 --- /dev/null +++ b/proj1-search-python3/layouts/smallClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%......%G G%......% +%.%%...%% %%...%%.% +%.%o.%........%.o%.% +%.%%.%.%%%%%%.%.%%.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/smallMaze.lay b/proj1-search-python3/layouts/smallMaze.lay new file mode 100644 index 0000000..72d3ffc --- /dev/null +++ b/proj1-search-python3/layouts/smallMaze.lay @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%% +% %% % % % +% %%%%%% % %%%%%% % +%%%%%% P % % +% % %%%%%% %% %%%%% +% %%%% % % % +% %%% %%% % % +%%%%%%%%%% %%%%%% % +%. %% % +%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/proj1-search-python3/layouts/smallSafeSearch.lay b/proj1-search-python3/layouts/smallSafeSearch.lay new file mode 100644 index 0000000..b97feaa --- /dev/null +++ b/proj1-search-python3/layouts/smallSafeSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%% +%.. % G % +%%% %%%%% +% % +%%%%%%% % +% % +% %%%%% % +% % % +%%%%% % % +% %o% +% %%%%%%% +% .% +%%%%%%%.% +%Po .% +%%%%%%%%% diff --git a/proj1-search-python3/layouts/smallSearch.lay b/proj1-search-python3/layouts/smallSearch.lay new file mode 100644 index 0000000..c2321d4 --- /dev/null +++ b/proj1-search-python3/layouts/smallSearch.lay @@ -0,0 +1,5 @@ +%%%%%%%%%%%%%%%%%%%% +%. ...P .% +%.%%.%%.%%.%%.%% %.% +% %% %..... %.% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/testClassic.lay b/proj1-search-python3/layouts/testClassic.lay new file mode 100644 index 0000000..4b3ffca --- /dev/null +++ b/proj1-search-python3/layouts/testClassic.lay @@ -0,0 +1,10 @@ +%%%%% +% . % +%.G.% +% . % +%. .% +% % +% .% +% % +%P .% +%%%%% diff --git a/proj1-search-python3/layouts/testMaze.lay b/proj1-search-python3/layouts/testMaze.lay new file mode 100644 index 0000000..4d259a4 --- /dev/null +++ b/proj1-search-python3/layouts/testMaze.lay @@ -0,0 +1,3 @@ +%%%%%%%%%% +%. P% +%%%%%%%%%% diff --git a/proj1-search-python3/layouts/testSearch.lay b/proj1-search-python3/layouts/testSearch.lay new file mode 100644 index 0000000..25bad23 --- /dev/null +++ b/proj1-search-python3/layouts/testSearch.lay @@ -0,0 +1,5 @@ +%%%%% +%.P % +%%% % +%. % +%%%%% diff --git a/proj1-search-python3/layouts/tinyCorners.lay b/proj1-search-python3/layouts/tinyCorners.lay new file mode 100644 index 0000000..526c880 --- /dev/null +++ b/proj1-search-python3/layouts/tinyCorners.lay @@ -0,0 +1,8 @@ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% diff --git a/proj1-search-python3/layouts/tinyMaze.lay b/proj1-search-python3/layouts/tinyMaze.lay new file mode 100644 index 0000000..f7035a5 --- /dev/null +++ b/proj1-search-python3/layouts/tinyMaze.lay @@ -0,0 +1,7 @@ +%%%%%%% +% P% +% %%% % +% % % +%% %% +%. %%%% +%%%%%%% diff --git a/proj1-search-python3/layouts/tinySafeSearch.lay b/proj1-search-python3/layouts/tinySafeSearch.lay new file mode 100644 index 0000000..fea6860 --- /dev/null +++ b/proj1-search-python3/layouts/tinySafeSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +% G %...% +%%%%%%% % +%Po % +%.%%.%%.% +%.%%....% +%%%%%%%%% diff --git a/proj1-search-python3/layouts/tinySearch.lay b/proj1-search-python3/layouts/tinySearch.lay new file mode 100644 index 0000000..c51f4b0 --- /dev/null +++ b/proj1-search-python3/layouts/tinySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +%.. ..% +%%%%.%% % +% P % +%.%% %%.% +%.%. .% +%%%%%%%%% diff --git a/proj1-search-python3/layouts/trappedClassic.lay b/proj1-search-python3/layouts/trappedClassic.lay new file mode 100644 index 0000000..289557f --- /dev/null +++ b/proj1-search-python3/layouts/trappedClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%% +% P G% +%G%%%%%% +%.... % +%%%%%%%% diff --git a/proj1-search-python3/layouts/trickyClassic.lay b/proj1-search-python3/layouts/trickyClassic.lay new file mode 100644 index 0000000..ffa156c --- /dev/null +++ b/proj1-search-python3/layouts/trickyClassic.lay @@ -0,0 +1,13 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%.%.....%..%.....%.% +%.%.%%.%% %%.%%.%.% +%...... GGGG%.%....% +%.%....%%%%%%.%..%.% +%.%....% oo%.%..%.% +%.%....% %%%%.%..%.% +%.%...........%..%.% +%.%%.%.%%%%%%.%.%%.% +%o...%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/layouts/trickySearch.lay b/proj1-search-python3/layouts/trickySearch.lay new file mode 100644 index 0000000..4a607e6 --- /dev/null +++ b/proj1-search-python3/layouts/trickySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% diff --git a/proj1-search-python3/pacman.py b/proj1-search-python3/pacman.py new file mode 100644 index 0000000..0a527c2 --- /dev/null +++ b/proj1-search-python3/pacman.py @@ -0,0 +1,684 @@ +# pacman.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +Pacman.py holds the logic for the classic pacman game along with the main +code to run a game. This file is divided into three sections: + + (i) Your interface to the pacman world: + Pacman is a complex environment. You probably don't want to + read through all of the code we wrote to make the game runs + correctly. This section contains the parts of the code + that you will need to understand in order to complete the + project. There is also some code in game.py that you should + understand. + + (ii) The hidden secrets of pacman: + This section contains all of the logic code that the pacman + environment uses to decide who can move where, who dies when + things collide, etc. You shouldn't need to read this section + of code, but you can if you want. + + (iii) Framework to start a game: + The final section contains the code for reading the command + you use to set up the game, then starting up a new game, along with + linking in all the external parts (agent functions, graphics). + Check this section out to see all the options available to you. + +To play your first game, type 'python pacman.py' from the command line. +The keys are 'a', 's', 'd', and 'w' to move (or arrow keys). Have fun! +""" +from game import GameStateData +from game import Game +from game import Directions +from game import Actions +from util import nearestPoint +from util import manhattanDistance +import util, layout +import sys, types, time, random, os + +################################################### +# YOUR INTERFACE TO THE PACMAN WORLD: A GameState # +################################################### + +class GameState: + """ + A GameState specifies the full game state, including the food, capsules, + agent configurations and score changes. + + GameStates are used by the Game object to capture the actual state of the game and + can be used by agents to reason about the game. + + Much of the information in a GameState is stored in a GameStateData object. We + strongly suggest that you access that data via the accessor methods below rather + than referring to the GameStateData object directly. + + Note that in classic Pacman, Pacman is always agent 0. + """ + + #################################################### + # Accessor methods: use these to access state data # + #################################################### + + # static variable keeps track of which states have had getLegalActions called + explored = set() + def getAndResetExplored(): + tmp = GameState.explored.copy() + GameState.explored = set() + return tmp + getAndResetExplored = staticmethod(getAndResetExplored) + + def getLegalActions( self, agentIndex=0 ): + """ + Returns the legal actions for the agent specified. + """ +# GameState.explored.add(self) + if self.isWin() or self.isLose(): return [] + + if agentIndex == 0: # Pacman is moving + return PacmanRules.getLegalActions( self ) + else: + return GhostRules.getLegalActions( self, agentIndex ) + + def generateSuccessor( self, agentIndex, action): + """ + Returns the successor state after the specified agent takes the action. + """ + # Check that successors exist + if self.isWin() or self.isLose(): raise Exception('Can\'t generate a successor of a terminal state.') + + # Copy current state + state = GameState(self) + + # Let agent's logic deal with its action's effects on the board + if agentIndex == 0: # Pacman is moving + state.data._eaten = [False for i in range(state.getNumAgents())] + PacmanRules.applyAction( state, action ) + else: # A ghost is moving + GhostRules.applyAction( state, action, agentIndex ) + + # Time passes + if agentIndex == 0: + state.data.scoreChange += -TIME_PENALTY # Penalty for waiting around + else: + GhostRules.decrementTimer( state.data.agentStates[agentIndex] ) + + # Resolve multi-agent effects + GhostRules.checkDeath( state, agentIndex ) + + # Book keeping + state.data._agentMoved = agentIndex + state.data.score += state.data.scoreChange + GameState.explored.add(self) + GameState.explored.add(state) + return state + + def getLegalPacmanActions( self ): + return self.getLegalActions( 0 ) + + def generatePacmanSuccessor( self, action ): + """ + Generates the successor state after the specified pacman move + """ + return self.generateSuccessor( 0, action ) + + def getPacmanState( self ): + """ + Returns an AgentState object for pacman (in game.py) + + state.pos gives the current position + state.direction gives the travel vector + """ + return self.data.agentStates[0].copy() + + def getPacmanPosition( self ): + return self.data.agentStates[0].getPosition() + + def getGhostStates( self ): + return self.data.agentStates[1:] + + def getGhostState( self, agentIndex ): + if agentIndex == 0 or agentIndex >= self.getNumAgents(): + raise Exception("Invalid index passed to getGhostState") + return self.data.agentStates[agentIndex] + + def getGhostPosition( self, agentIndex ): + if agentIndex == 0: + raise Exception("Pacman's index passed to getGhostPosition") + return self.data.agentStates[agentIndex].getPosition() + + def getGhostPositions(self): + return [s.getPosition() for s in self.getGhostStates()] + + def getNumAgents( self ): + return len( self.data.agentStates ) + + def getScore( self ): + return float(self.data.score) + + def getCapsules(self): + """ + Returns a list of positions (x,y) of the remaining capsules. + """ + return self.data.capsules + + def getNumFood( self ): + return self.data.food.count() + + def getFood(self): + """ + Returns a Grid of boolean food indicator variables. + + Grids can be accessed via list notation, so to check + if there is food at (x,y), just call + + currentFood = state.getFood() + if currentFood[x][y] == True: ... + """ + return self.data.food + + def getWalls(self): + """ + Returns a Grid of boolean wall indicator variables. + + Grids can be accessed via list notation, so to check + if there is a wall at (x,y), just call + + walls = state.getWalls() + if walls[x][y] == True: ... + """ + return self.data.layout.walls + + def hasFood(self, x, y): + return self.data.food[x][y] + + def hasWall(self, x, y): + return self.data.layout.walls[x][y] + + def isLose( self ): + return self.data._lose + + def isWin( self ): + return self.data._win + + ############################################# + # Helper methods: # + # You shouldn't need to call these directly # + ############################################# + + def __init__( self, prevState = None ): + """ + Generates a new state by copying information from its predecessor. + """ + if prevState != None: # Initial state + self.data = GameStateData(prevState.data) + else: + self.data = GameStateData() + + def deepCopy( self ): + state = GameState( self ) + state.data = self.data.deepCopy() + return state + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + return hasattr(other, 'data') and self.data == other.data + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + return hash( self.data ) + + def __str__( self ): + + return str(self.data) + + def initialize( self, layout, numGhostAgents=1000 ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.data.initialize(layout, numGhostAgents) + +############################################################################ +# THE HIDDEN SECRETS OF PACMAN # +# # +# You shouldn't need to look through the code in this section of the file. # +############################################################################ + +SCARED_TIME = 40 # Moves ghosts are scared +COLLISION_TOLERANCE = 0.7 # How close ghosts must be to Pacman to kill +TIME_PENALTY = 1 # Number of points lost each round + +class ClassicGameRules: + """ + These game rules manage the control flow of a game, deciding when + and how the game starts and ends. + """ + def __init__(self, timeout=30): + self.timeout = timeout + + def newGame( self, layout, pacmanAgent, ghostAgents, display, quiet = False, catchExceptions=False): + agents = [pacmanAgent] + ghostAgents[:layout.getNumGhosts()] + initState = GameState() + initState.initialize( layout, len(ghostAgents) ) + game = Game(agents, display, self, catchExceptions=catchExceptions) + game.state = initState + self.initialState = initState.deepCopy() + self.quiet = quiet + return game + + def process(self, state, game): + """ + Checks to see whether it is time to end the game. + """ + if state.isWin(): self.win(state, game) + if state.isLose(): self.lose(state, game) + + def win( self, state, game ): + if not self.quiet: print("Pacman emerges victorious! Score: %d" % state.data.score) + game.gameOver = True + + def lose( self, state, game ): + if not self.quiet: print("Pacman died! Score: %d" % state.data.score) + game.gameOver = True + + def getProgress(self, game): + return float(game.state.getNumFood()) / self.initialState.getNumFood() + + def agentCrash(self, game, agentIndex): + if agentIndex == 0: + print("Pacman crashed") + else: + print("A ghost crashed") + + def getMaxTotalTime(self, agentIndex): + return self.timeout + + def getMaxStartupTime(self, agentIndex): + return self.timeout + + def getMoveWarningTime(self, agentIndex): + return self.timeout + + def getMoveTimeout(self, agentIndex): + return self.timeout + + def getMaxTimeWarnings(self, agentIndex): + return 0 + +class PacmanRules: + """ + These functions govern how pacman interacts with his environment under + the classic game rules. + """ + PACMAN_SPEED=1 + + def getLegalActions( state ): + """ + Returns a list of possible actions. + """ + return Actions.getPossibleActions( state.getPacmanState().configuration, state.data.layout.walls ) + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action ): + """ + Edits the state to reflect the results of the action. + """ + legal = PacmanRules.getLegalActions( state ) + if action not in legal: + raise Exception("Illegal action " + str(action)) + + pacmanState = state.data.agentStates[0] + + # Update Configuration + vector = Actions.directionToVector( action, PacmanRules.PACMAN_SPEED ) + pacmanState.configuration = pacmanState.configuration.generateSuccessor( vector ) + + # Eat + next = pacmanState.configuration.getPosition() + nearest = nearestPoint( next ) + if manhattanDistance( nearest, next ) <= 0.5 : + # Remove food + PacmanRules.consume( nearest, state ) + applyAction = staticmethod( applyAction ) + + def consume( position, state ): + x,y = position + # Eat food + if state.data.food[x][y]: + state.data.scoreChange += 10 + state.data.food = state.data.food.copy() + state.data.food[x][y] = False + state.data._foodEaten = position + # TODO: cache numFood? + numFood = state.getNumFood() + if numFood == 0 and not state.data._lose: + state.data.scoreChange += 500 + state.data._win = True + # Eat capsule + if( position in state.getCapsules() ): + state.data.capsules.remove( position ) + state.data._capsuleEaten = position + # Reset all ghosts' scared timers + for index in range( 1, len( state.data.agentStates ) ): + state.data.agentStates[index].scaredTimer = SCARED_TIME + consume = staticmethod( consume ) + +class GhostRules: + """ + These functions dictate how ghosts interact with their environment. + """ + GHOST_SPEED=1.0 + def getLegalActions( state, ghostIndex ): + """ + Ghosts cannot stop, and cannot turn around unless they + reach a dead end, but can turn 90 degrees at intersections. + """ + conf = state.getGhostState( ghostIndex ).configuration + possibleActions = Actions.getPossibleActions( conf, state.data.layout.walls ) + reverse = Actions.reverseDirection( conf.direction ) + if Directions.STOP in possibleActions: + possibleActions.remove( Directions.STOP ) + if reverse in possibleActions and len( possibleActions ) > 1: + possibleActions.remove( reverse ) + return possibleActions + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action, ghostIndex): + + legal = GhostRules.getLegalActions( state, ghostIndex ) + if action not in legal: + raise Exception("Illegal ghost action " + str(action)) + + ghostState = state.data.agentStates[ghostIndex] + speed = GhostRules.GHOST_SPEED + if ghostState.scaredTimer > 0: speed /= 2.0 + vector = Actions.directionToVector( action, speed ) + ghostState.configuration = ghostState.configuration.generateSuccessor( vector ) + applyAction = staticmethod( applyAction ) + + def decrementTimer( ghostState): + timer = ghostState.scaredTimer + if timer == 1: + ghostState.configuration.pos = nearestPoint( ghostState.configuration.pos ) + ghostState.scaredTimer = max( 0, timer - 1 ) + decrementTimer = staticmethod( decrementTimer ) + + def checkDeath( state, agentIndex): + pacmanPosition = state.getPacmanPosition() + if agentIndex == 0: # Pacman just moved; Anyone can kill him + for index in range( 1, len( state.data.agentStates ) ): + ghostState = state.data.agentStates[index] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, index ) + else: + ghostState = state.data.agentStates[agentIndex] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, agentIndex ) + checkDeath = staticmethod( checkDeath ) + + def collide( state, ghostState, agentIndex): + if ghostState.scaredTimer > 0: + state.data.scoreChange += 200 + GhostRules.placeGhost(state, ghostState) + ghostState.scaredTimer = 0 + # Added for first-person + state.data._eaten[agentIndex] = True + else: + if not state.data._win: + state.data.scoreChange -= 500 + state.data._lose = True + collide = staticmethod( collide ) + + def canKill( pacmanPosition, ghostPosition ): + return manhattanDistance( ghostPosition, pacmanPosition ) <= COLLISION_TOLERANCE + canKill = staticmethod( canKill ) + + def placeGhost(state, ghostState): + ghostState.configuration = ghostState.start + placeGhost = staticmethod( placeGhost ) + +############################# +# FRAMEWORK TO START A GAME # +############################# + +def default(str): + return str + ' [Default: %default]' + +def parseAgentArgs(str): + if str == None: return {} + pieces = str.split(',') + opts = {} + for p in pieces: + if '=' in p: + key, val = p.split('=') + else: + key,val = p, 1 + opts[key] = val + return opts + +def readCommand( argv ): + """ + Processes the command used to run pacman from the command line. + """ + from optparse import OptionParser + usageStr = """ + USAGE: python pacman.py + EXAMPLES: (1) python pacman.py + - starts an interactive game + (2) python pacman.py --layout smallClassic --zoom 2 + OR python pacman.py -l smallClassic -z 2 + - starts an interactive game on a smaller board, zoomed in + """ + parser = OptionParser(usageStr) + + parser.add_option('-n', '--numGames', dest='numGames', type='int', + help=default('the number of GAMES to play'), metavar='GAMES', default=1) + parser.add_option('-l', '--layout', dest='layout', + help=default('the LAYOUT_FILE from which to load the map layout'), + metavar='LAYOUT_FILE', default='mediumClassic') + parser.add_option('-p', '--pacman', dest='pacman', + help=default('the agent TYPE in the pacmanAgents module to use'), + metavar='TYPE', default='KeyboardAgent') + parser.add_option('-t', '--textGraphics', action='store_true', dest='textGraphics', + help='Display output as text only', default=False) + parser.add_option('-q', '--quietTextGraphics', action='store_true', dest='quietGraphics', + help='Generate minimal output and no graphics', default=False) + parser.add_option('-g', '--ghosts', dest='ghost', + help=default('the ghost agent TYPE in the ghostAgents module to use'), + metavar = 'TYPE', default='RandomGhost') + parser.add_option('-k', '--numghosts', type='int', dest='numGhosts', + help=default('The maximum number of ghosts to use'), default=4) + parser.add_option('-z', '--zoom', type='float', dest='zoom', + help=default('Zoom the size of the graphics window'), default=1.0) + parser.add_option('-f', '--fixRandomSeed', action='store_true', dest='fixRandomSeed', + help='Fixes the random seed to always play the same game', default=False) + parser.add_option('-r', '--recordActions', action='store_true', dest='record', + help='Writes game histories to a file (named by the time they were played)', default=False) + parser.add_option('--replay', dest='gameToReplay', + help='A recorded game file (pickle) to replay', default=None) + parser.add_option('-a','--agentArgs',dest='agentArgs', + help='Comma separated values sent to agent. e.g. "opt1=val1,opt2,opt3=val3"') + parser.add_option('-x', '--numTraining', dest='numTraining', type='int', + help=default('How many episodes are training (suppresses output)'), default=0) + parser.add_option('--frameTime', dest='frameTime', type='float', + help=default('Time to delay between frames; <0 means keyboard'), default=0.1) + parser.add_option('-c', '--catchExceptions', action='store_true', dest='catchExceptions', + help='Turns on exception handling and timeouts during games', default=False) + parser.add_option('--timeout', dest='timeout', type='int', + help=default('Maximum length of time an agent can spend computing in a single game'), default=30) + + options, otherjunk = parser.parse_args(argv) + if len(otherjunk) != 0: + raise Exception('Command line input not understood: ' + str(otherjunk)) + args = dict() + + # Fix the random seed + if options.fixRandomSeed: random.seed('cs188') + + # Choose a layout + args['layout'] = layout.getLayout( options.layout ) + if args['layout'] == None: raise Exception("The layout " + options.layout + " cannot be found") + + # Choose a Pacman agent + noKeyboard = options.gameToReplay == None and (options.textGraphics or options.quietGraphics) + pacmanType = loadAgent(options.pacman, noKeyboard) + agentOpts = parseAgentArgs(options.agentArgs) + if options.numTraining > 0: + args['numTraining'] = options.numTraining + if 'numTraining' not in agentOpts: agentOpts['numTraining'] = options.numTraining + pacman = pacmanType(**agentOpts) # Instantiate Pacman with agentArgs + args['pacman'] = pacman + + # Don't display training games + if 'numTrain' in agentOpts: + options.numQuiet = int(agentOpts['numTrain']) + options.numIgnore = int(agentOpts['numTrain']) + + # Choose a ghost agent + ghostType = loadAgent(options.ghost, noKeyboard) + args['ghosts'] = [ghostType( i+1 ) for i in range( options.numGhosts )] + + # Choose a display format + if options.quietGraphics: + import textDisplay + args['display'] = textDisplay.NullGraphics() + elif options.textGraphics: + import textDisplay + textDisplay.SLEEP_TIME = options.frameTime + args['display'] = textDisplay.PacmanGraphics() + else: + import graphicsDisplay + args['display'] = graphicsDisplay.PacmanGraphics(options.zoom, frameTime = options.frameTime) + args['numGames'] = options.numGames + args['record'] = options.record + args['catchExceptions'] = options.catchExceptions + args['timeout'] = options.timeout + + # Special case: recorded games don't use the runGames method or args structure + if options.gameToReplay != None: + print('Replaying recorded game %s.' % options.gameToReplay) + import pickle + f = open(options.gameToReplay, 'rb') + try: recorded = pickle.load(f) + finally: f.close() + recorded['display'] = args['display'] + replayGame(**recorded) + sys.exit(0) + + return args + +def loadAgent(pacman, nographics): + # Looks through all pythonPath Directories for the right module, + pythonPathStr = os.path.expandvars("$PYTHONPATH") + if pythonPathStr.find(';') == -1: + pythonPathDirs = pythonPathStr.split(':') + else: + pythonPathDirs = pythonPathStr.split(';') + pythonPathDirs.append('.') + + for moduleDir in pythonPathDirs: + if not os.path.isdir(moduleDir): continue + moduleNames = [f for f in os.listdir(moduleDir) if f.endswith('gents.py')] + for modulename in moduleNames: + try: + module = __import__(modulename[:-3]) + except ImportError: + continue + if pacman in dir(module): + if nographics and modulename == 'keyboardAgents.py': + raise Exception('Using the keyboard requires graphics (not text display)') + return getattr(module, pacman) + raise Exception('The agent ' + pacman + ' is not specified in any *Agents.py.') + +def replayGame( layout, actions, display ): + import pacmanAgents, ghostAgents + rules = ClassicGameRules() + agents = [pacmanAgents.GreedyAgent()] + [ghostAgents.RandomGhost(i+1) for i in range(layout.getNumGhosts())] + game = rules.newGame( layout, agents[0], agents[1:], display ) + state = game.state + display.initialize(state.data) + + for action in actions: + # Execute the action + state = state.generateSuccessor( *action ) + # Change the display + display.update( state.data ) + # Allow for game specific conditions (winning, losing, etc.) + rules.process(state, game) + + display.finish() + +def runGames( layout, pacman, ghosts, display, numGames, record, numTraining = 0, catchExceptions=False, timeout=30 ): + import __main__ + __main__.__dict__['_display'] = display + + rules = ClassicGameRules(timeout) + games = [] + + for i in range( numGames ): + beQuiet = i < numTraining + if beQuiet: + # Suppress output and graphics + import textDisplay + gameDisplay = textDisplay.NullGraphics() + rules.quiet = True + else: + gameDisplay = display + rules.quiet = False + game = rules.newGame( layout, pacman, ghosts, gameDisplay, beQuiet, catchExceptions) + game.run() + if not beQuiet: games.append(game) + + if record: + import time, pickle + fname = ('recorded-game-%d' % (i + 1)) + '-'.join([str(t) for t in time.localtime()[1:6]]) + f = open(fname, 'wb') + components = {'layout': layout, 'actions': game.moveHistory} + pickle.dump(components, f) + f.close() + + if (numGames-numTraining) > 0: + scores = [game.state.getScore() for game in games] + wins = [game.state.isWin() for game in games] + winRate = wins.count(True)/ float(len(wins)) + print('Average Score:', sum(scores) / float(len(scores))) + print('Scores: ', ', '.join([str(score) for score in scores])) + print('Win Rate: %d/%d (%.2f)' % (wins.count(True), len(wins), winRate)) + print('Record: ', ', '.join([ ['Loss', 'Win'][int(w)] for w in wins])) + + return games + +if __name__ == '__main__': + """ + The main function called when pacman.py is run + from the command line: + + > python pacman.py + + See the usage string for more details. + + > python pacman.py --help + """ + args = readCommand( sys.argv[1:] ) # Get game components based on input + runGames( **args ) + + # import cProfile + # cProfile.run("runGames( **args )") + pass diff --git a/proj1-search-python3/pacmanAgents.py b/proj1-search-python3/pacmanAgents.py new file mode 100644 index 0000000..ae97634 --- /dev/null +++ b/proj1-search-python3/pacmanAgents.py @@ -0,0 +1,52 @@ +# pacmanAgents.py +# --------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from pacman import Directions +from game import Agent +import random +import game +import util + +class LeftTurnAgent(game.Agent): + "An agent that turns left at every opportunity" + + def getAction(self, state): + legal = state.getLegalPacmanActions() + current = state.getPacmanState().configuration.direction + if current == Directions.STOP: current = Directions.NORTH + left = Directions.LEFT[current] + if left in legal: return left + if current in legal: return current + if Directions.RIGHT[current] in legal: return Directions.RIGHT[current] + if Directions.LEFT[left] in legal: return Directions.LEFT[left] + return Directions.STOP + +class GreedyAgent(Agent): + def __init__(self, evalFn="scoreEvaluation"): + self.evaluationFunction = util.lookup(evalFn, globals()) + assert self.evaluationFunction != None + + def getAction(self, state): + # Generate candidate actions + legal = state.getLegalPacmanActions() + if Directions.STOP in legal: legal.remove(Directions.STOP) + + successors = [(state.generateSuccessor(0, action), action) for action in legal] + scored = [(self.evaluationFunction(state), action) for state, action in successors] + bestScore = max(scored)[0] + bestActions = [pair[1] for pair in scored if pair[0] == bestScore] + return random.choice(bestActions) + +def scoreEvaluation(state): + return state.getScore() diff --git a/proj1-search-python3/projectParams.py b/proj1-search-python3/projectParams.py new file mode 100644 index 0000000..dc3e9d1 --- /dev/null +++ b/proj1-search-python3/projectParams.py @@ -0,0 +1,18 @@ +# projectParams.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +STUDENT_CODE_DEFAULT = 'searchAgents.py,search.py' +PROJECT_TEST_CLASSES = 'searchTestClasses.py' +PROJECT_NAME = 'Project 1: Search' +BONUS_PIC = False diff --git a/proj1-search-python3/search.py b/proj1-search-python3/search.py new file mode 100644 index 0000000..91227da --- /dev/null +++ b/proj1-search-python3/search.py @@ -0,0 +1,205 @@ +# search.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +In search.py, you will implement generic search algorithms which are called by +Pacman agents (in searchAgents.py). +""" + +import util + +class SearchProblem: + """ + This class outlines the structure of a search problem, but doesn't implement + any of the methods (in object-oriented terminology: an abstract class). + + You do not need to change anything in this class, ever. + """ + + def getStartState(self): + """ + Returns the start state for the search problem. + """ + util.raiseNotDefined() + + def isGoalState(self, state): + """ + state: Search state + + Returns True if and only if the state is a valid goal state. + """ + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + state: Search state + + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' is + the incremental cost of expanding to that successor. + """ + util.raiseNotDefined() + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. + The sequence must be composed of legal moves. + """ + util.raiseNotDefined() + + +def tinyMazeSearch(problem): + """ + Returns a sequence of moves that solves tinyMaze. For any other maze, the + sequence of moves will be incorrect, so only use this for tinyMaze. + """ + from game import Directions + s = Directions.SOUTH + w = Directions.WEST + return [s, s, w, s, w, w, s, w] + +def depthFirstSearch(problem): + """ + Search the deepest nodes in the search tree first. + + Your search algorithm needs to return a list of actions that reaches the + goal. Make sure to implement a graph search algorithm. + + To get started, you might want to try some of these simple commands to + understand the search problem that is being passed in: + + print("Start:", problem.getStartState()) + print("Is the start a goal?", problem.isGoalState(problem.getStartState())) + print("Start's successors:", problem.getSuccessors(problem.getStartState())) + """ + "*** YOUR CODE HERE ***" + from util import Stack + + frontier=util.Stack() + visited=[] + frontier.push((problem.getStartState(),[])) + + while not frontier.isEmpty(): + cur_node,actions=frontier.pop() + if problem.isGoalState(cur_node): + return actions + + if cur_node not in visited: + expand=problem.getSuccessors(cur_node) + visited.append(cur_node) + for location,direction,cost in expand: + if location not in visited: + frontier.push((location,actions+[direction])) + util.raiseNotDefined() + +def breadthFirstSearch(problem): + """Search the shallowest nodes in the search tree first.""" + "*** YOUR CODE HERE ***" + frontier=util.Queue() + visited=[] + frontier.push((problem.getStartState(),[])) + + while not frontier.isEmpty(): + cur_node,actions=frontier.pop() + if problem.isGoalState(cur_node): + return actions + + if cur_node not in visited: + expand=problem.getSuccessors(cur_node) + visited.append(cur_node) + for location,direction,cost in expand: + if location not in visited: + frontier.push((location,actions+[direction])) + util.raiseNotDefined() + +def uniformCostSearch(problem): + """Search the node of least total cost first.""" + "*** YOUR CODE HERE ***" + frontier = util.PriorityQueueWithFunction(lambda x: x[2]) + visited=[] + frontier.push((problem.getStartState(),None,0)) + + path = [] + parentSeq = {} + parentSeq[(problem.getStartState(),None,0)]=None + while not frontier.isEmpty(): + current_fullstate=frontier.pop() + # print(current_fullstate) + cur_node=current_fullstate[0] + actions=current_fullstate[1] + if problem.isGoalState(cur_node): + break + + if cur_node not in visited: + expand=problem.getSuccessors(cur_node) + visited.append(cur_node) + for state in expand: + location = state[0] + direction = state[1] + cost=current_fullstate[2]+state[2] + if location not in visited: + frontier.push((location,direction,cost)) + parentSeq[(location,direction)] = current_fullstate + # elif location in visited: + # frontier.update((location,direction,cost)) + child = current_fullstate + + while (child != None): + path.append(child[1]) + if child[0] != problem.getStartState(): + child = parentSeq[(child[0],child[1])] + else: + child = None + path.reverse() + return path[1:] + util.raiseNotDefined() + +def nullHeuristic(state, problem=None): + """ + A heuristic function estimates the cost from the current state to the nearest + goal in the provided SearchProblem. This heuristic is trivial. + """ + return 0 + +def aStarSearch(problem, heuristic=nullHeuristic): + """Search the node that has the lowest combined cost and heuristic first.""" + "*** YOUR CODE HERE ***" + frontier = util.PriorityQueue() + actions = [] + frontier.push((problem.getStartState(),actions),0) + visited = [] + + while frontier: + cur_node,actions = frontier.pop() + if problem.isGoalState(cur_node): + return actions + if cur_node not in visited: + visited.append(cur_node) + expand = problem.getSuccessors(cur_node) + for successor, action, cost in expand: + tempActions = actions + [action] + nextCost = problem.getCostOfActions(tempActions) + heuristic(successor,problem) + if successor not in visited: + frontier.push((successor,tempActions),nextCost) + util.raiseNotDefined() + + +# Abbreviations +bfs = breadthFirstSearch +dfs = depthFirstSearch +astar = aStarSearch +ucs = uniformCostSearch diff --git a/proj1-search-python3/searchAgents.py b/proj1-search-python3/searchAgents.py new file mode 100644 index 0000000..c8c518c --- /dev/null +++ b/proj1-search-python3/searchAgents.py @@ -0,0 +1,575 @@ +# searchAgents.py +# --------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +This file contains all of the agents that can be selected to control Pacman. To +select an agent, use the '-p' option when running pacman.py. Arguments can be +passed to your agent using '-a'. For example, to load a SearchAgent that uses +depth first search (dfs), run the following command: + +> python pacman.py -p SearchAgent -a fn=depthFirstSearch + +Commands to invoke other search strategies can be found in the project +description. + +Please only change the parts of the file you are asked to. Look for the lines +that say + +"*** YOUR CODE HERE ***" + +The parts you fill in start about 3/4 of the way down. Follow the project +description for details. + +Good luck and happy searching! +""" + +from game import Directions +from game import Agent +from game import Actions +import util +import time +import search + +class GoWestAgent(Agent): + "An agent that goes West until it can't." + + def getAction(self, state): + "The agent receives a GameState (defined in pacman.py)." + if Directions.WEST in state.getLegalPacmanActions(): + return Directions.WEST + else: + return Directions.STOP + +####################################################### +# This portion is written for you, but will only work # +# after you fill in parts of search.py # +####################################################### + +class SearchAgent(Agent): + """ + This very general search agent finds a path using a supplied search + algorithm for a supplied search problem, then returns actions to follow that + path. + + As a default, this agent runs DFS on a PositionSearchProblem to find + location (1,1) + + Options for fn include: + depthFirstSearch or dfs + breadthFirstSearch or bfs + + + Note: You should NOT change any code in SearchAgent + """ + + def __init__(self, fn='depthFirstSearch', prob='PositionSearchProblem', heuristic='nullHeuristic'): + # Warning: some advanced Python magic is employed below to find the right functions and problems + + # Get the search function from the name and heuristic + if fn not in dir(search): + raise AttributeError(fn + ' is not a search function in search.py.') + func = getattr(search, fn) + if 'heuristic' not in func.__code__.co_varnames: + print('[SearchAgent] using function ' + fn) + self.searchFunction = func + else: + if heuristic in globals().keys(): + heur = globals()[heuristic] + elif heuristic in dir(search): + heur = getattr(search, heuristic) + else: + raise AttributeError(heuristic + ' is not a function in searchAgents.py or search.py.') + print('[SearchAgent] using function %s and heuristic %s' % (fn, heuristic)) + # Note: this bit of Python trickery combines the search algorithm and the heuristic + self.searchFunction = lambda x: func(x, heuristic=heur) + + # Get the search problem type from the name + if prob not in globals().keys() or not prob.endswith('Problem'): + raise AttributeError(prob + ' is not a search problem type in SearchAgents.py.') + self.searchType = globals()[prob] + print('[SearchAgent] using problem type ' + prob) + + def registerInitialState(self, state): + """ + This is the first time that the agent sees the layout of the game + board. Here, we choose a path to the goal. In this phase, the agent + should compute the path to the goal and store it in a local variable. + All of the work is done in this method! + + state: a GameState object (pacman.py) + """ + if self.searchFunction == None: raise Exception("No search function provided for SearchAgent") + starttime = time.time() + problem = self.searchType(state) # Makes a new search problem + self.actions = self.searchFunction(problem) # Find a path + totalCost = problem.getCostOfActions(self.actions) + print('Path found with total cost of %d in %.1f seconds' % (totalCost, time.time() - starttime)) + if '_expanded' in dir(problem): print('Search nodes expanded: %d' % problem._expanded) + + def getAction(self, state): + """ + Returns the next action in the path chosen earlier (in + registerInitialState). Return Directions.STOP if there is no further + action to take. + + state: a GameState object (pacman.py) + """ + if 'actionIndex' not in dir(self): self.actionIndex = 0 + i = self.actionIndex + self.actionIndex += 1 + if i < len(self.actions): + return self.actions[i] + else: + return Directions.STOP + +class PositionSearchProblem(search.SearchProblem): + """ + A search problem defines the state space, start state, goal test, successor + function and cost function. This search problem can be used to find paths + to a particular point on the pacman board. + + The state space consists of (x,y) positions in a pacman game. + + Note: this search problem is fully specified; you should NOT change it. + """ + + def __init__(self, gameState, costFn = lambda x: 1, goal=(1,1), start=None, warn=True, visualize=True): + """ + Stores the start and goal. + + gameState: A GameState object (pacman.py) + costFn: A function from a search state (tuple) to a non-negative number + goal: A position in the gameState + """ + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + if start != None: self.startState = start + self.goal = goal + self.costFn = costFn + self.visualize = visualize + if warn and (gameState.getNumFood() != 1 or not gameState.hasFood(*goal)): + print('Warning: this does not look like a regular search maze') + + # For display purposes + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def getStartState(self): + return self.startState + + def isGoalState(self, state): + isGoal = state == self.goal + + # For display purposes only + if isGoal and self.visualize: + self._visitedlist.append(state) + import __main__ + if '_display' in dir(__main__): + if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable + __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable + + return isGoal + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, + (successor, action, stepCost), where 'successor' is a + successor to the current state, 'action' is the action + required to get there, and 'stepCost' is the incremental + cost of expanding to that successor + """ + + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextState = (nextx, nexty) + cost = self.costFn(nextState) + successors.append( ( nextState, action, cost) ) + + # Bookkeeping for display purposes + self._expanded += 1 # DO NOT CHANGE + if state not in self._visited: + self._visited[state] = True + self._visitedlist.append(state) + + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. + """ + if actions == None: return 999999 + x,y= self.getStartState() + cost = 0 + for action in actions: + # Check figure out the next state and see whether its' legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + cost += self.costFn((x,y)) + return cost + +class StayEastSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the West side of the board. + + The cost function for stepping into a position (x,y) is 1/2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn, (1, 1), None, False) + +class StayWestSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the East side of the board. + + The cost function for stepping into a position (x,y) is 2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: 2 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn) + +def manhattanHeuristic(position, problem, info={}): + "The Manhattan distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) + +def euclideanHeuristic(position, problem, info={}): + "The Euclidean distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 + +##################################################### +# This portion is incomplete. Time to write code! # +##################################################### + +class CornersProblem(search.SearchProblem): + """ + This search problem finds paths through all four corners of a layout. + + You must select a suitable state space and successor function + """ + + def __init__(self, startingGameState): + """ + Stores the walls, pacman's starting position and corners. + """ + self.walls = startingGameState.getWalls() + self.startingPosition = startingGameState.getPacmanPosition() + top, right = self.walls.height-2, self.walls.width-2 + self.corners = ((1,1), (1,top), (right, 1), (right, top)) + for corner in self.corners: + if not startingGameState.hasFood(*corner): + print('Warning: no food in corner ' + str(corner)) + self._expanded = 0 # DO NOT CHANGE; Number of search nodes expanded + # Please add any code here which you would like to use + # in initializing the problem + "*** YOUR CODE HERE ***" + + def getStartState(self): + """ + Returns the start state (in your state space, not the full Pacman state + space) + """ + "*** YOUR CODE HERE ***" + return (self.startingPosition,[]) + util.raiseNotDefined() + + def isGoalState(self, state): + """ + Returns whether this search state is a goal state of the problem. + """ + "*** YOUR CODE HERE ***" + pos = state[0] + Visited_Corners = state[1] + + return len(Visited_Corners)==4 + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' + is the incremental cost of expanding to that successor + """ + x,y = state[0] + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + # Add a successor state to the successor list if the action is legal + # Here's a code snippet for figuring out whether a new position hits a wall: + # x,y = currentPosition + # dx, dy = Actions.directionToVector(action) + # nextx, nexty = int(x + dx), int(y + dy) + # hitsWall = self.walls[nextx][nexty] + + "*** YOUR CODE HERE ***" + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + next_node = (nextx, nexty) + hitsWall = self.walls[nextx][nexty] + if not hitsWall: + list_vis_corner = list(state[1]) + if next_node in self.corners: + if next_node not in list_vis_corner: + list_vis_corner.append( next_node ) + successor = ((next_node, list_vis_corner), action, 1) + successors.append(successor) + self._expanded += 1 # DO NOT CHANGE + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. This is implemented for you. + """ + if actions == None: return 999999 + x,y= self.startingPosition + for action in actions: + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + return len(actions) + +def minmanhattan(corners, pos): + if len(corners) == 0: + return 0 + + hn = [] + for loc in corners: + dis = abs(loc[0] - pos[0]) + abs(loc[1] - pos[1])+ minmanhattan([c for c in corners if c != loc], loc) + hn.append(dis) + + return min(hn) +def cornersHeuristic(state, problem): + """ + A heuristic for the CornersProblem that you defined. + + state: The current search state + (a data structure you chose in your search problem) + + problem: The CornersProblem instance for this layout. + + This function should always return a number that is a lower bound on the + shortest path from the state to a goal of the problem; i.e. it should be + admissible (as well as consistent). + """ + corners = problem.corners # These are the corner coordinates + walls = problem.walls # These are the walls of the maze, as a Grid (game.py) + + "*** YOUR CODE HERE ***" + return minmanhattan([item for item in corners if item not in state[1]],state[0]) + return 0 # Default to trivial solution + +class AStarCornersAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic) + self.searchType = CornersProblem + +class FoodSearchProblem: + """ + A search problem associated with finding the a path that collects all of the + food (dots) in a Pacman game. + + A search state in this problem is a tuple ( pacmanPosition, foodGrid ) where + pacmanPosition: a tuple (x,y) of integers specifying Pacman's position + foodGrid: a Grid (see game.py) of either True or False, specifying remaining food + """ + def __init__(self, startingGameState): + self.start = (startingGameState.getPacmanPosition(), startingGameState.getFood()) + self.walls = startingGameState.getWalls() + self.startingGameState = startingGameState + self._expanded = 0 # DO NOT CHANGE + self.heuristicInfo = {} # A dictionary for the heuristic to store information + + def getStartState(self): + return self.start + + def isGoalState(self, state): + return state[1].count() == 0 + + def getSuccessors(self, state): + "Returns successor states, the actions they require, and a cost of 1." + successors = [] + self._expanded += 1 # DO NOT CHANGE + for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state[0] + dx, dy = Actions.directionToVector(direction) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextFood = state[1].copy() + nextFood[nextx][nexty] = False + successors.append( ( ((nextx, nexty), nextFood), direction, 1) ) + return successors + + def getCostOfActions(self, actions): + """Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999""" + x,y= self.getStartState()[0] + cost = 0 + for action in actions: + # figure out the next state and see whether it's legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: + return 999999 + cost += 1 + return cost + +class AStarFoodSearchAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, foodHeuristic) + self.searchType = FoodSearchProblem + +def foodHeuristic(state, problem): + """ + Your heuristic for the FoodSearchProblem goes here. + + This heuristic must be consistent to ensure correctness. First, try to come + up with an admissible heuristic; almost all admissible heuristics will be + consistent as well. + + If using A* ever finds a solution that is worse uniform cost search finds, + your heuristic is *not* consistent, and probably not admissible! On the + other hand, inadmissible or inconsistent heuristics may find optimal + solutions, so be careful. + + The state is a tuple ( pacmanPosition, foodGrid ) where foodGrid is a Grid + (see game.py) of either True or False. You can call foodGrid.asList() to get + a list of food coordinates instead. + + If you want access to info like walls, capsules, etc., you can query the + problem. For example, problem.walls gives you a Grid of where the walls + are. + + If you want to *store* information to be reused in other calls to the + heuristic, there is a dictionary called problem.heuristicInfo that you can + use. For example, if you only want to count the walls once and store that + value, try: problem.heuristicInfo['wallCount'] = problem.walls.count() + Subsequent calls to this heuristic can access + problem.heuristicInfo['wallCount'] + """ + position, foodGrid = state + "*** YOUR CODE HERE ***" + food_coordinates = foodGrid.asList() + if not food_coordinates: + return 0 + res = -1 + for tmp in food_coordinates: + dist = mazeDistance(tmp, position, problem.startingGameState) + res = max(res,dist) + return res + return 0 + +class ClosestDotSearchAgent(SearchAgent): + "Search for all food using a sequence of searches" + def registerInitialState(self, state): + self.actions = [] + currentState = state + while(currentState.getFood().count() > 0): + nextPathSegment = self.findPathToClosestDot(currentState) # The missing piece + self.actions += nextPathSegment + for action in nextPathSegment: + legal = currentState.getLegalActions() + if action not in legal: + t = (str(action), str(currentState)) + raise Exception('findPathToClosestDot returned an illegal move: %s!\n%s' % t) + currentState = currentState.generateSuccessor(0, action) + self.actionIndex = 0 + print('Path found with cost %d.' % len(self.actions)) + + def findPathToClosestDot(self, gameState): + """ + Returns a path (a list of actions) to the closest dot, starting from + gameState. + """ + # Here are some useful elements of the startState + startPosition = gameState.getPacmanPosition() + food = gameState.getFood() + walls = gameState.getWalls() + problem = AnyFoodSearchProblem(gameState) + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +class AnyFoodSearchProblem(PositionSearchProblem): + """ + A search problem for finding a path to any food. + + This search problem is just like the PositionSearchProblem, but has a + different goal test, which you need to fill in below. The state space and + successor function do not need to be changed. + + The class definition above, AnyFoodSearchProblem(PositionSearchProblem), + inherits the methods of the PositionSearchProblem. + + You can use this search problem to help you fill in the findPathToClosestDot + method. + """ + + def __init__(self, gameState): + "Stores information from the gameState. You don't need to change this." + # Store the food for later reference + self.food = gameState.getFood() + + # Store info for the PositionSearchProblem (no need to change this) + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + self.costFn = lambda x: 1 + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def isGoalState(self, state): + """ + The state is Pacman's position. Fill this in with a goal test that will + complete the problem definition. + """ + x,y = state + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +def mazeDistance(point1, point2, gameState): + """ + Returns the maze distance between any two points, using the search functions + you have already built. The gameState can be any game state -- Pacman's + position in that state is ignored. + + Example usage: mazeDistance( (2,4), (5,6), gameState) + + This might be a useful helper function for your ApproximateSearchAgent. + """ + x1, y1 = point1 + x2, y2 = point2 + walls = gameState.getWalls() + assert not walls[x1][y1], 'point1 is a wall: ' + str(point1) + assert not walls[x2][y2], 'point2 is a wall: ' + str(point2) + prob = PositionSearchProblem(gameState, start=point1, goal=point2, warn=False, visualize=False) + return len(search.bfs(prob)) diff --git a/proj1-search-python3/searchTestClasses.py b/proj1-search-python3/searchTestClasses.py new file mode 100644 index 0000000..f2a01af --- /dev/null +++ b/proj1-search-python3/searchTestClasses.py @@ -0,0 +1,823 @@ +# searchTestClasses.py +# -------------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import re +import testClasses +import textwrap + +# import project specific code +import layout +import pacman +from search import SearchProblem + +# helper function for printing solutions in solution files +def wrap_solution(solution): + if type(solution) == type([]): + return '\n'.join(textwrap.wrap(' '.join(solution))) + else: + return str(solution) + + + + +def followAction(state, action, problem): + for successor1, action1, cost1 in problem.getSuccessors(state): + if action == action1: return successor1 + return None + +def followPath(path, problem): + state = problem.getStartState() + states = [state] + for action in path: + state = followAction(state, action, problem) + states.append(state) + return states + +def checkSolution(problem, path): + state = problem.getStartState() + for action in path: + state = followAction(state, action, problem) + return problem.isGoalState(state) + +# Search problem on a plain graph +class GraphSearch(SearchProblem): + + # Read in the state graph; define start/end states, edges and costs + def __init__(self, graph_text): + self.expanded_states = [] + lines = graph_text.split('\n') + r = re.match('start_state:(.*)', lines[0]) + if r == None: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("GraphSearch graph specification start_state not found or incorrect on line 0") + self.start_state = r.group(1).strip() + r = re.match('goal_states:(.*)', lines[1]) + if r == None: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("GraphSearch graph specification goal_states not found or incorrect on line 1") + goals = r.group(1).split() + self.goals = [str.strip(g) for g in goals] + self.successors = {} + all_states = set() + self.orderedSuccessorTuples = [] + for l in lines[2:]: + if len(l.split()) == 3: + start, action, next_state = l.split() + cost = 1 + elif len(l.split()) == 4: + start, action, next_state, cost = l.split() + else: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("Invalid line in GraphSearch graph specification on line:" + l) + cost = float(cost) + self.orderedSuccessorTuples.append((start, action, next_state, cost)) + all_states.add(start) + all_states.add(next_state) + if start not in self.successors: + self.successors[start] = [] + self.successors[start].append((next_state, action, cost)) + for s in all_states: + if s not in self.successors: + self.successors[s] = [] + + # Get start state + def getStartState(self): + return self.start_state + + # Check if a state is a goal state + def isGoalState(self, state): + return state in self.goals + + # Get all successors of a state + def getSuccessors(self, state): + self.expanded_states.append(state) + return list(self.successors[state]) + + # Calculate total cost of a sequence of actions + def getCostOfActions(self, actions): + total_cost = 0 + state = self.start_state + for a in actions: + successors = self.successors[state] + match = False + for (next_state, action, cost) in successors: + if a == action: + state = next_state + total_cost += cost + match = True + if not match: + print('invalid action sequence') + sys.exit(1) + return total_cost + + # Return a list of all states on which 'getSuccessors' was called + def getExpandedStates(self): + return self.expanded_states + + def __str__(self): + print(self.successors) + edges = ["%s %s %s %s" % t for t in self.orderedSuccessorTuples] + return \ +"""start_state: %s +goal_states: %s +%s""" % (self.start_state, " ".join(self.goals), "\n".join(edges)) + + + +def parseHeuristic(heuristicText): + heuristic = {} + for line in heuristicText.split('\n'): + tokens = line.split() + if len(tokens) != 2: + print("Broken heuristic:") + print('"""%s"""' % heuristicText) + raise Exception("GraphSearch heuristic specification broken at tokens:" + str(tokens)) + state, h = tokens + heuristic[state] = float(h) + + def graphHeuristic(state, problem=None): + if state in heuristic: + return heuristic[state] + else: + import pprint + pp = pprint.PrettyPrinter(indent=4) + print("Heuristic:") + pp.pprint(heuristic) + raise Exception("Graph heuristic called with invalid state: " + str(state)) + + return graphHeuristic + + +class GraphSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(GraphSearchTest, self).__init__(question, testDict) + self.graph_text = testDict['graph'] + self.alg = testDict['algorithm'] + self.diagram = testDict['diagram'] + self.exactExpansionOrder = testDict.get('exactExpansionOrder', 'True').lower() == "true" + if 'heuristic' in testDict: + self.heuristic = parseHeuristic(testDict['heuristic']) + else: + self.heuristic = None + + # Note that the return type of this function is a tripple: + # (solution, expanded states, error message) + def getSolInfo(self, search): + alg = getattr(search, self.alg) + problem = GraphSearch(self.graph_text) + if self.heuristic != None: + solution = alg(problem, self.heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + return solution, problem.getExpandedStates(), None + + # Run student code. If an error message is returned, print error and return false. + # If a good solution is returned, printn the solution and return true; otherwise, + # print both the correct and student's solution and return false. + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded_states = [str.split(solutionDict['expanded_states']), str.split(solutionDict['rev_expanded_states'])] + + solution, expanded_states, error = self.getSolInfo(search) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\t%s' % error) + return False + + if solution in gold_solution and (not self.exactExpansionOrder or expanded_states in gold_expanded_states): + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tsolution:\t\t%s' % solution) + grades.addMessage('\texpanded_states:\t%s' % expanded_states) + return True + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tgraph:') + for line in self.diagram.split('\n'): + grades.addMessage('\t %s' % (line,)) + grades.addMessage('\tstudent solution:\t\t%s' % solution) + grades.addMessage('\tstudent expanded_states:\t%s' % expanded_states) + grades.addMessage('') + grades.addMessage('\tcorrect solution:\t\t%s' % gold_solution[0]) + grades.addMessage('\tcorrect expanded_states:\t%s' % gold_expanded_states[0]) + grades.addMessage('\tcorrect rev_solution:\t\t%s' % gold_solution[1]) + grades.addMessage('\tcorrect rev_expanded_states:\t%s' % gold_expanded_states[1]) + return False + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + + # write forward solution + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: "%s"\n' % ' '.join(solution)) + handle.write('expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # reverse and write backwards solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: "%s"\n' % ' '.join(solution)) + handle.write('rev_expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + + +class PacmanSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(PacmanSearchTest, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + self.alg = testDict['algorithm'] + self.layoutName = testDict['layoutName'] + + # TODO: sensible to have defaults like this? + self.leewayFactor = float(testDict.get('leewayFactor', '1')) + self.costFn = eval(testDict.get('costFn', 'None')) + self.searchProblemClassName = testDict.get('searchProblemClass', 'PositionSearchProblem') + self.heuristicName = testDict.get('heuristic', None) + + + def getSolInfo(self, search, searchAgents): + alg = getattr(search, self.alg) + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + + problemClass = getattr(searchAgents, self.searchProblemClassName) + problemOptions = {} + if self.costFn != None: + problemOptions['costFn'] = self.costFn + problem = problemClass(start_state, **problemOptions) + heuristic = getattr(searchAgents, self.heuristicName) if self.heuristicName != None else None + + if heuristic != None: + solution = alg(problem, heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + from game import Directions + dirs = Directions.LEFT.keys() + if [el in dirs for el in solution].count(False) != 0: + return None, None, 'Output of %s must be a list of actions from game.Directions' % self.alg + + expanded = problem._expanded + return solution, expanded, None + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded = max(int(solutionDict['expanded_nodes']), int(solutionDict['rev_expanded_nodes'])) + + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % error) + return False + + # FIXME: do we want to standardize test output format? + + if solution not in gold_solution: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Solution not correct.') + grades.addMessage('\tstudent solution length: %s' % len(solution)) + grades.addMessage('\tstudent solution:\n%s' % wrap_solution(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length: %s' % len(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution length: %s' % len(gold_solution[1])) + grades.addMessage('\tcorrect solution:\n%s' % wrap_solution(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution:\n%s' % wrap_solution(gold_solution[1])) + return False + + if expanded > self.leewayFactor * gold_expanded and expanded > gold_expanded + 1: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Too many node expanded; are you expanding nodes twice?') + grades.addMessage('\tstudent nodes expanded: %s' % expanded) + grades.addMessage('') + grades.addMessage('\tcorrect nodes expanded: %s (leewayFactor %s)' % (gold_expanded, self.leewayFactor)) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length: %s' % len(solution)) + grades.addMessage('\tnodes expanded:\t\t%s' % expanded) + return True + + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + handle.write('# Number of nodes expanded must be with a factor of %s of the numbers below.\n' % self.leewayFactor) + + # write forward solution + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('expanded_nodes: "%s"\n' % expanded) + + # write backward solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('rev_expanded_nodes: "%s"\n' % expanded) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + +from game import Actions +def getStatesFromPath(start, path): + "Returns the list of states visited along the path" + vis = [start] + curr = start + for a in path: + x,y = curr + dx, dy = Actions.directionToVector(a) + curr = (int(x + dx), int(y + dy)) + vis.append(curr) + return vis + +class CornerProblemTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerProblemTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, search, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problem = searchAgents.CornersProblem(gameState) + path = search.bfs(problem) + + gameState = pacman.GameState() + gameState.initialize(lay, 0) + visited = getStatesFromPath(gameState.getPacmanPosition(), path) + top, right = gameState.getWalls().height-2, gameState.getWalls().width-2 + missedCorners = [p for p in ((1,1), (1,top), (right, 1), (right, top)) if p not in visited] + + return path, missedCorners + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution, missedCorners = self.solution(search, searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('The result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(missedCorners) != 0: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Corners missed: %s' % missedCorners) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Optimal solution not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName) + print(self.layoutText) + + path, _ = self.solution(search, searchAgents) + length = len(path) + print("Problem solved") + + handle.write('solution_length: "%s"\n' % length) + handle.close() + + + + +# template = """class: "HeuristicTest" +# +# heuristic: "foodHeuristic" +# searchProblemClass: "FoodSearchProblem" +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(doneTests + foodTests): +# f = open("food_heuristic_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class HeuristicTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + def checkHeuristic(self, heuristic, problem, state, solutionCost): + h0 = heuristic(state, problem) + + if solutionCost == 0: + if h0 == 0: + return True, '' + else: + return False, 'Heuristic failed H(goal) == 0 test' + + if h0 < 0: + return False, 'Heuristic failed H >= 0 test' + if not h0 > 0: + return False, 'Heuristic failed non-triviality test' + if not h0 <= solutionCost: + return False, 'Heuristic failed admissibility test' + + for succ, action, stepCost in problem.getSuccessors(state): + h1 = heuristic(succ, problem) + if h1 < 0: return False, 'Heuristic failed H >= 0 test' + if h0 - h1 > stepCost: return False, 'Heuristic failed consistency test' + + return True, '' + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + solutionCost = int(solutionDict['solution_cost']) + problem, state, heuristic = self.setupProblem(searchAgents) + + passed, message = self.checkHeuristic(heuristic, problem, state, solutionCost) + + if not passed: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % message) + return False + else: + grades.addMessage('PASS: %s' % self.path) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName, self.heuristicName) + print(self.layoutText) + problem, _, heuristic = self.setupProblem(searchAgents) + path = search.astar(problem, heuristic) + cost = problem.getCostOfActions(path) + print("Problem solved") + + handle.write('solution_cost: "%s"\n' % cost) + handle.close() + return True + + + + + + +class HeuristicGrade(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicGrade, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + self.basePoints = int(testDict['basePoints']) + self.thresholds = [int(t) for t in testDict['gradingThresholds'].split()] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + problem, _, heuristic = self.setupProblem(searchAgents) + + path = search.astar(problem, heuristic) + + expanded = problem._expanded + + if not checkSolution(problem, path): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tReturned path is not a solution.') + grades.addMessage('\tpath returned by astar: %s' % expanded) + return False + + grades.addPoints(self.basePoints) + points = 0 + for threshold in self.thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(self.thresholds): + grades.addMessage('PASS: %s' % self.path) + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\texpanded nodes: %s' % expanded) + grades.addMessage('\tthresholds: %s' % self.thresholds) + + return True + + + def writeSolution(self, moduleDict, filePath): + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# File intentionally blank.\n') + handle.close() + return True + + + + + +# template = """class: "ClosestDotTest" +# +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(foodTests): +# f = open("closest_dot_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class ClosestDotTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(ClosestDotTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + path = searchAgents.ClosestDotSearchAgent().findPathToClosestDot(gameState) + return path + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution = self.solution(searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tThe result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Closest dot not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName) + print(self.layoutText) + + length = len(self.solution(searchAgents)) + print("Problem solved") + + handle.write('solution_length: "%s"\n' % length) + handle.close() + return True + + + + +class CornerHeuristicSanity(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicSanity, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + h0 = searchAgents.cornersHeuristic(start_state, problem) + succs = problem.getSuccessors(start_state) + # cornerConsistencyA + for succ in succs: + h1 = searchAgents.cornersHeuristic(succ[0], problem) + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + heuristic_cost = searchAgents.cornersHeuristic(start_state, problem) + true_cost = float(solutionDict['cost']) + # cornerNontrivial + if heuristic_cost == 0: + grades.addMessage('FAIL: must use non-trivial heuristic') + return False + # cornerAdmissible + if heuristic_cost > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = solutionDict['path'].split() + states = followPath(path, problem) + heuristics = [] + for state in states: + heuristics.append(searchAgents.cornersHeuristic(state, problem)) + for i in range(0, len(heuristics) - 1): + h0 = heuristics[i] + h1 = heuristics[i+1] + # cornerConsistencyB + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + # cornerPosH + if h0 < 0 or h1 <0: + grades.addMessage('FAIL: non-positive heuristic') + return False + # cornerGoalH + if heuristics[len(heuristics) - 1] != 0: + grades.addMessage('FAIL: heuristic non-zero at goal') + return False + grades.addMessage('PASS: heuristic value less than true cost at start state') + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# In order for a heuristic to be admissible, the value\n') + handle.write('# of the heuristic must be less at each state than the\n') + handle.write('# true cost of the optimal path from that state to a goal.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.close() + return True + + + +class CornerHeuristicPacman(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicPacman, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + total = 0 + true_cost = float(solutionDict['cost']) + thresholds = [int(x) for x in solutionDict['thresholds'].split()] + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + if searchAgents.cornersHeuristic(start_state, problem) > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = search.astar(problem, searchAgents.cornersHeuristic) + print("path:", path) + print("path length:", len(path)) + cost = problem.getCostOfActions(path) + if cost > true_cost: + grades.addMessage('FAIL: Inconsistent heuristic') + return False + expanded = problem._expanded + points = 0 + for threshold in thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(thresholds): + grades.addMessage('PASS: Heuristic resulted in expansion of %d nodes' % expanded) + else: + grades.addMessage('FAIL: Heuristic resulted in expansion of %d nodes' % expanded) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# This solution file specifies the length of the optimal path\n') + handle.write('# as well as the thresholds on number of nodes expanded to be\n') + handle.write('# used in scoring.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('thresholds: "2000 1600 1200"\n') + handle.close() + return True + diff --git a/proj1-search-python3/testClasses.py b/proj1-search-python3/testClasses.py new file mode 100644 index 0000000..39ee87c --- /dev/null +++ b/proj1-search-python3/testClasses.py @@ -0,0 +1,206 @@ +# testClasses.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# import modules from python standard library +import inspect +import re +import sys + + +# Class which models a question in a project. Note that questions have a +# maximum number of points they are worth, and are composed of a series of +# test cases +class Question(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def __init__(self, questionDict, display): + self.maxPoints = int(questionDict['max_points']) + self.testCases = [] + self.display = display + + def getDisplay(self): + return self.display + + def getMaxPoints(self): + return self.maxPoints + + # Note that 'thunk' must be a function which accepts a single argument, + # namely a 'grading' object + def addTestCase(self, testCase, thunk): + self.testCases.append((testCase, thunk)) + + def execute(self, grades): + self.raiseNotDefined() + +# Question in which all test cases must be passed in order to receive credit +class PassAllTestsQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + +class ExtraCreditPassAllTestsQuestion(Question): + def __init__(self, questionDict, display): + Question.__init__(self, questionDict, display) + self.extraPoints = int(questionDict['extra_points']) + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + grades.addPoints(self.extraPoints) + +# Question in which predict credit is given for test cases with a ``points'' property. +# All other tests are mandatory and must be passed. +class HackedPartialCreditQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + grades.assignZeroCredit() + + points = 0 + passed = True + for testCase, f in self.testCases: + testResult = f(grades) + if "points" in testCase.testDict: + if testResult: points += float(testCase.testDict["points"]) + else: + passed = passed and testResult + + ## FIXME: Below terrible hack to match q3's logic + if int(points) == self.maxPoints and not passed: + grades.assignZeroCredit() + else: + grades.addPoints(int(points)) + + +class Q6PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + results = [] + for _, f in self.testCases: + results.append(f(grades)) + if False in results: + grades.assignZeroCredit() + +class PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + for _, f in self.testCases: + if not f(grades): + grades.assignZeroCredit() + grades.fail("Tests failed.") + return False + + + +class NumberPassedQuestion(Question): + """Grade is the number of test cases passed.""" + + def execute(self, grades): + grades.addPoints([f(grades) for _, f in self.testCases].count(True)) + + + + + +# Template modeling a generic test case +class TestCase(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def getPath(self): + return self.path + + def __init__(self, question, testDict): + self.question = question + self.testDict = testDict + self.path = testDict['path'] + self.messages = [] + + def __str__(self): + self.raiseNotDefined() + + def execute(self, grades, moduleDict, solutionDict): + self.raiseNotDefined() + + def writeSolution(self, moduleDict, filePath): + self.raiseNotDefined() + return True + + # Tests should call the following messages for grading + # to ensure a uniform format for test output. + # + # TODO: this is hairy, but we need to fix grading.py's interface + # to get a nice hierarchical project - question - test structure, + # then these should be moved into Question proper. + def testPass(self, grades): + grades.addMessage('PASS: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return True + + def testFail(self, grades): + grades.addMessage('FAIL: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return False + + # This should really be question level? + # + def testPartial(self, grades, points, maxPoints): + grades.addPoints(points) + extraCredit = max(0, points - maxPoints) + regularCredit = points - extraCredit + + grades.addMessage('%s: %s (%s of %s points)' % ("PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints)) + if extraCredit > 0: + grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,)) + + for line in self.messages: + grades.addMessage(' %s' % (line,)) + + return True + + def addMessage(self, message): + self.messages.extend(message.split('\n')) + diff --git a/proj1-search-python3/testParser.py b/proj1-search-python3/testParser.py new file mode 100644 index 0000000..17f3485 --- /dev/null +++ b/proj1-search-python3/testParser.py @@ -0,0 +1,85 @@ +# testParser.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import re +import sys + +class TestParser(object): + + def __init__(self, path): + # save the path to the test file + self.path = path + + def removeComments(self, rawlines): + # remove any portion of a line following a '#' symbol + fixed_lines = [] + for l in rawlines: + idx = l.find('#') + if idx == -1: + fixed_lines.append(l) + else: + fixed_lines.append(l[0:idx]) + return '\n'.join(fixed_lines) + + def parse(self): + # read in the test case and remove comments + test = {} + with open(self.path) as handle: + raw_lines = handle.read().split('\n') + + test_text = self.removeComments(raw_lines) + test['__raw_lines__'] = raw_lines + test['path'] = self.path + test['__emit__'] = [] + lines = test_text.split('\n') + i = 0 + # read a property in each loop cycle + while(i < len(lines)): + # skip blank lines + if re.match('\A\s*\Z', lines[i]): + test['__emit__'].append(("raw", raw_lines[i])) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"([^"]*)"\s*\Z', lines[i]) + if m: + test[m.group(1)] = m.group(2) + test['__emit__'].append(("oneline", m.group(1))) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"""\s*\Z', lines[i]) + if m: + msg = [] + i += 1 + while(not re.match('\A\s*"""\s*\Z', lines[i])): + msg.append(raw_lines[i]) + i += 1 + test[m.group(1)] = '\n'.join(msg) + test['__emit__'].append(("multiline", m.group(1))) + i += 1 + continue + print('error parsing test file: %s' % self.path) + sys.exit(1) + return test + + +def emitTestDict(testDict, handle): + for kind, data in testDict['__emit__']: + if kind == "raw": + handle.write(data + "\n") + elif kind == "oneline": + handle.write('%s: "%s"\n' % (data, testDict[data])) + elif kind == "multiline": + handle.write('%s: """\n%s\n"""\n' % (data, testDict[data])) + else: + raise Exception("Bad __emit__") diff --git a/proj1-search-python3/test_cases/CONFIG b/proj1-search-python3/test_cases/CONFIG new file mode 100644 index 0000000..dbed66b --- /dev/null +++ b/proj1-search-python3/test_cases/CONFIG @@ -0,0 +1 @@ +order: "q1 q2 q3 q4 q5 q6 q7 q8" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q1/CONFIG b/proj1-search-python3/test_cases/q1/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/proj1-search-python3/test_cases/q1/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/proj1-search-python3/test_cases/q1/graph_backtrack.solution b/proj1-search-python3/test_cases/q1/graph_backtrack.solution new file mode 100644 index 0000000..c52850c --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A D C" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q1/graph_backtrack.test b/proj1-search-python3/test_cases/q1/graph_backtrack.test new file mode 100644 index 0000000..05640a0 --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.solution b/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..0680f92 --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->D 0:D->G" +expanded_states: "A D" +rev_solution: "0:A->B 0:B->D 0:D->G" +rev_expanded_states: "A B D" diff --git a/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.test b/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..155e1fe --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/proj1-search-python3/test_cases/q1/graph_infinite.solution b/proj1-search-python3/test_cases/q1/graph_infinite.solution new file mode 100644 index 0000000..82203ee --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q1/graph_infinite.test b/proj1-search-python3/test_cases/q1/graph_infinite.test new file mode 100644 index 0000000..692ac05 --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/proj1-search-python3/test_cases/q1/graph_manypaths.solution b/proj1-search-python3/test_cases/q1/graph_manypaths.solution new file mode 100644 index 0000000..34b5a82 --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->B2 0:B2->C 0:C->D 2:D->E2 0:E2->F 0:F->G" +expanded_states: "A B2 C D E2 F" +rev_solution: "0:A->B1 0:B1->C 0:C->D 0:D->E1 0:E1->F 0:F->G" +rev_expanded_states: "A B1 C D E1 F" diff --git a/proj1-search-python3/test_cases/q1/graph_manypaths.test b/proj1-search-python3/test_cases/q1/graph_manypaths.test new file mode 100644 index 0000000..953c4eb --- /dev/null +++ b/proj1-search-python3/test_cases/q1/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/proj1-search-python3/test_cases/q1/pacman_1.solution b/proj1-search-python3/test_cases/q1/pacman_1.solution new file mode 100644 index 0000000..82a670c --- /dev/null +++ b/proj1-search-python3/test_cases/q1/pacman_1.solution @@ -0,0 +1,40 @@ +# This is the solution file for test_cases/q1/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East East East East East East East South South South +East East East East East East East South South South South South South +South West West West West West West West West West West West West West +West West West West South West West West West West West West West West +""" +expanded_nodes: "146" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South South East North East East East South +South South West West West West West West West North North North North +North North North North West West West West West West West North North +North East East East East South East East East North North North West +West North North West West West West West West West West West West +West West West West West West West West West West West West West West +South South South South South South South South South East East East +North North North North North North North East East South South South +South South South East East North North North North North North East +East South South South South East East North North North North East +East East East East South South West West West South South East East +East South South West West West West West West South South West West +West West West South West West West West West South South East East +East East East East East North East East East East East North North +East East East East East East North East East East East East South +South West West West South West West West West West West South South +West West West West West South West West West West West West West West +West +""" +rev_expanded_nodes: "269" diff --git a/proj1-search-python3/test_cases/q1/pacman_1.test b/proj1-search-python3/test_cases/q1/pacman_1.test new file mode 100644 index 0000000..6ae5412 --- /dev/null +++ b/proj1-search-python3/test_cases/q1/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic depth first search test +class: "PacmanSearchTest" +algorithm: "depthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q2/CONFIG b/proj1-search-python3/test_cases/q2/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/proj1-search-python3/test_cases/q2/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/proj1-search-python3/test_cases/q2/graph_backtrack.solution b/proj1-search-python3/test_cases/q2/graph_backtrack.solution new file mode 100644 index 0000000..6c669c2 --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A D C B" diff --git a/proj1-search-python3/test_cases/q2/graph_backtrack.test b/proj1-search-python3/test_cases/q2/graph_backtrack.test new file mode 100644 index 0000000..2b35d8b --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.solution b/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..05eecc8 --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A D" diff --git a/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.test b/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..47b78a6 --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/proj1-search-python3/test_cases/q2/graph_infinite.solution b/proj1-search-python3/test_cases/q2/graph_infinite.solution new file mode 100644 index 0000000..17b621c --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q2/graph_infinite.test b/proj1-search-python3/test_cases/q2/graph_infinite.test new file mode 100644 index 0000000..2cae9ad --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/proj1-search-python3/test_cases/q2/graph_manypaths.solution b/proj1-search-python3/test_cases/q2/graph_manypaths.solution new file mode 100644 index 0000000..0cea422 --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B2 C B1 D E2 F E1" diff --git a/proj1-search-python3/test_cases/q2/graph_manypaths.test b/proj1-search-python3/test_cases/q2/graph_manypaths.test new file mode 100644 index 0000000..7c636ea --- /dev/null +++ b/proj1-search-python3/test_cases/q2/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/proj1-search-python3/test_cases/q2/pacman_1.solution b/proj1-search-python3/test_cases/q2/pacman_1.solution new file mode 100644 index 0000000..8f6d2bd --- /dev/null +++ b/proj1-search-python3/test_cases/q2/pacman_1.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q2/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/proj1-search-python3/test_cases/q2/pacman_1.test b/proj1-search-python3/test_cases/q2/pacman_1.test new file mode 100644 index 0000000..c913f0c --- /dev/null +++ b/proj1-search-python3/test_cases/q2/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic breadth first search test +class: "PacmanSearchTest" +algorithm: "breadthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q3/CONFIG b/proj1-search-python3/test_cases/q3/CONFIG new file mode 100644 index 0000000..e5332c3 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" diff --git a/proj1-search-python3/test_cases/q3/graph_backtrack.solution b/proj1-search-python3/test_cases/q3/graph_backtrack.solution new file mode 100644 index 0000000..d150cb7 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/proj1-search-python3/test_cases/q3/graph_backtrack.test b/proj1-search-python3/test_cases/q3/graph_backtrack.test new file mode 100644 index 0000000..a74bd9e --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.solution b/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..5dfffca --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A B" diff --git a/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.test b/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..87aa465 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/proj1-search-python3/test_cases/q3/graph_infinite.solution b/proj1-search-python3/test_cases/q3/graph_infinite.solution new file mode 100644 index 0000000..c6cd195 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q3/graph_infinite.test b/proj1-search-python3/test_cases/q3/graph_infinite.test new file mode 100644 index 0000000..80d807f --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/proj1-search-python3/test_cases/q3/graph_manypaths.solution b/proj1-search-python3/test_cases/q3/graph_manypaths.solution new file mode 100644 index 0000000..628568f --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/proj1-search-python3/test_cases/q3/graph_manypaths.test b/proj1-search-python3/test_cases/q3/graph_manypaths.test new file mode 100644 index 0000000..8c39dc7 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/proj1-search-python3/test_cases/q3/ucs_0_graph.solution b/proj1-search-python3/test_cases/q3/ucs_0_graph.solution new file mode 100644 index 0000000..b8c1509 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_0_graph.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_0_graph.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/proj1-search-python3/test_cases/q3/ucs_0_graph.test b/proj1-search-python3/test_cases/q3/ucs_0_graph.test new file mode 100644 index 0000000..e8f3d4c --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_0_graph.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + |1 + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/proj1-search-python3/test_cases/q3/ucs_1_problemC.solution b/proj1-search-python3/test_cases/q3/ucs_1_problemC.solution new file mode 100644 index 0000000..dc8fc4c --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_1_problemC.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_1_problemC.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/proj1-search-python3/test_cases/q3/ucs_1_problemC.test b/proj1-search-python3/test_cases/q3/ucs_1_problemC.test new file mode 100644 index 0000000..1ce714d --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_1_problemC.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +#costFn: "lambda pos: 1" diff --git a/proj1-search-python3/test_cases/q3/ucs_2_problemE.solution b/proj1-search-python3/test_cases/q3/ucs_2_problemE.solution new file mode 100644 index 0000000..d84245f --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_2_problemE.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_2_problemE.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +expanded_nodes: "260" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +rev_expanded_nodes: "260" diff --git a/proj1-search-python3/test_cases/q3/ucs_2_problemE.test b/proj1-search-python3/test_cases/q3/ucs_2_problemE.test new file mode 100644 index 0000000..3c609f2 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_2_problemE.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: .5 ** pos[0]" diff --git a/proj1-search-python3/test_cases/q3/ucs_3_problemW.solution b/proj1-search-python3/test_cases/q3/ucs_3_problemW.solution new file mode 100644 index 0000000..e04325d --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_3_problemW.solution @@ -0,0 +1,34 @@ +# This is the solution file for test_cases/q3/ucs_3_problemW.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +expanded_nodes: "173" +rev_solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +rev_expanded_nodes: "173" diff --git a/proj1-search-python3/test_cases/q3/ucs_3_problemW.test b/proj1-search-python3/test_cases/q3/ucs_3_problemW.test new file mode 100644 index 0000000..fbc2fad --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_3_problemW.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: 2 ** pos[0]" diff --git a/proj1-search-python3/test_cases/q3/ucs_4_testSearch.solution b/proj1-search-python3/test_cases/q3/ucs_4_testSearch.solution new file mode 100644 index 0000000..b8c5303 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_4_testSearch.solution @@ -0,0 +1,12 @@ +# This is the solution file for test_cases/q3/ucs_4_testSearch.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 2.0 of the numbers below. +solution: """ +West East East South South West West +""" +expanded_nodes: "14" +rev_solution: """ +West East East South South West West +""" +rev_expanded_nodes: "13" diff --git a/proj1-search-python3/test_cases/q3/ucs_4_testSearch.test b/proj1-search-python3/test_cases/q3/ucs_4_testSearch.test new file mode 100644 index 0000000..a16c6d8 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_4_testSearch.test @@ -0,0 +1,16 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "testSearch" +layout: """ +%%%%% +%.P % +%%% % +%. % +%%%%% +""" +searchProblemClass: "FoodSearchProblem" +leewayFactor: "2" + diff --git a/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.solution b/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.solution new file mode 100644 index 0000000..7d6c982 --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_5_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.test b/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.test new file mode 100644 index 0000000..72b35bc --- /dev/null +++ b/proj1-search-python3/test_cases/q3/ucs_5_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/proj1-search-python3/test_cases/q4/CONFIG b/proj1-search-python3/test_cases/q4/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/proj1-search-python3/test_cases/q4/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q4/astar_0.solution b/proj1-search-python3/test_cases/q4/astar_0.solution new file mode 100644 index 0000000..459cadd --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_0.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_0.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/proj1-search-python3/test_cases/q4/astar_0.test b/proj1-search-python3/test_cases/q4/astar_0.test new file mode 100644 index 0000000..9b3b539 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_0.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + | + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.solution b/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.solution new file mode 100644 index 0000000..7767c27 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_1_graph_heuristic.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0 0 2" +expanded_states: "S A D C" +rev_solution: "0 0 2" +rev_expanded_states: "S A D C" diff --git a/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.test b/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.test new file mode 100644 index 0000000..b5afd79 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_1_graph_heuristic.test @@ -0,0 +1,54 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 2 3 2 + S --- A --- C ---> G + | \ / ^ +3 | \ 5 / 1 / + | \ / / + B --- D -------/ + 4 5 + +S is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +The heuristic value of each state is: + S 6.0 + A 2.5 + B 5.25 + C 1.125 + D 1.0625 + G 0 +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: S +goal_states: G +S 0 A 2.0 +S 1 B 3.0 +S 2 D 5.0 +A 0 C 3.0 +A 1 S 2.0 +B 0 D 4.0 +B 1 S 3.0 +C 0 A 3.0 +C 1 D 1.0 +C 2 G 2.0 +D 0 B 4.0 +D 1 C 1.0 +D 2 G 5.0 +D 3 S 5.0 +""" +heuristic: """ +S 6.0 +A 2.5 +B 5.25 +C 1.125 +D 1.0625 +G 0 +""" diff --git a/proj1-search-python3/test_cases/q4/astar_2_manhattan.solution b/proj1-search-python3/test_cases/q4/astar_2_manhattan.solution new file mode 100644 index 0000000..65bf5f5 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_2_manhattan.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q4/astar_2_manhattan.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "221" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "221" diff --git a/proj1-search-python3/test_cases/q4/astar_2_manhattan.test b/proj1-search-python3/test_cases/q4/astar_2_manhattan.test new file mode 100644 index 0000000..e936195 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_2_manhattan.test @@ -0,0 +1,27 @@ +class: "PacmanSearchTest" +algorithm: "aStarSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +heuristic: "manhattanHeuristic" diff --git a/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.solution b/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.solution new file mode 100644 index 0000000..edb3502 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_3_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.test b/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.test new file mode 100644 index 0000000..c4d1903 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/astar_3_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/proj1-search-python3/test_cases/q4/graph_backtrack.solution b/proj1-search-python3/test_cases/q4/graph_backtrack.solution new file mode 100644 index 0000000..fc51794 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/proj1-search-python3/test_cases/q4/graph_backtrack.test b/proj1-search-python3/test_cases/q4/graph_backtrack.test new file mode 100644 index 0000000..84e0126 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/proj1-search-python3/test_cases/q4/graph_manypaths.solution b/proj1-search-python3/test_cases/q4/graph_manypaths.solution new file mode 100644 index 0000000..0caa767 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/proj1-search-python3/test_cases/q4/graph_manypaths.test b/proj1-search-python3/test_cases/q4/graph_manypaths.test new file mode 100644 index 0000000..82fdf87 --- /dev/null +++ b/proj1-search-python3/test_cases/q4/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/proj1-search-python3/test_cases/q5/CONFIG b/proj1-search-python3/test_cases/q5/CONFIG new file mode 100644 index 0000000..e7c6582 --- /dev/null +++ b/proj1-search-python3/test_cases/q5/CONFIG @@ -0,0 +1,3 @@ +class: "PassAllTestsQuestion" +max_points: "3" +depends: "q2" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q5/corner_tiny_corner.solution b/proj1-search-python3/test_cases/q5/corner_tiny_corner.solution new file mode 100644 index 0000000..161bf15 --- /dev/null +++ b/proj1-search-python3/test_cases/q5/corner_tiny_corner.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q5/corner_tiny_corner.test. +solution_length: "28" diff --git a/proj1-search-python3/test_cases/q5/corner_tiny_corner.test b/proj1-search-python3/test_cases/q5/corner_tiny_corner.test new file mode 100644 index 0000000..823bd47 --- /dev/null +++ b/proj1-search-python3/test_cases/q5/corner_tiny_corner.test @@ -0,0 +1,14 @@ +class: "CornerProblemTest" + +layoutName: "tinyCorner" +layout: """ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q6/CONFIG b/proj1-search-python3/test_cases/q6/CONFIG new file mode 100644 index 0000000..b76e0eb --- /dev/null +++ b/proj1-search-python3/test_cases/q6/CONFIG @@ -0,0 +1,3 @@ +class: "Q6PartialCreditQuestion" +max_points: "3" +depends: "q4" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_1.solution b/proj1-search-python3/test_cases/q6/corner_sanity_1.solution new file mode 100644 index 0000000..4385d9b --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_1.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +North South South East East East North North +""" diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_1.test b/proj1-search-python3/test_cases/q6/corner_sanity_1.test new file mode 100644 index 0000000..93379ac --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_1.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +%P % +%. .% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_2.solution b/proj1-search-python3/test_cases/q6/corner_sanity_2.solution new file mode 100644 index 0000000..1aebe8a --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_2.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +West North North East East East South South +""" diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_2.test b/proj1-search-python3/test_cases/q6/corner_sanity_2.test new file mode 100644 index 0000000..18184a8 --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_2.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +% %% % +%.P%.% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_3.solution b/proj1-search-python3/test_cases/q6/corner_sanity_3.solution new file mode 100644 index 0000000..c02dd57 --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_3.solution @@ -0,0 +1,9 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "28" +path: """ +South South South West West West West East East East East East North +North North North North West West West South South South West West +North North North +""" diff --git a/proj1-search-python3/test_cases/q6/corner_sanity_3.test b/proj1-search-python3/test_cases/q6/corner_sanity_3.test new file mode 100644 index 0000000..8f30442 --- /dev/null +++ b/proj1-search-python3/test_cases/q6/corner_sanity_3.test @@ -0,0 +1,15 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%%%% +%.% .% +% % % % +% % %P % +% % % +%%%%% % +%. .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q6/medium_corners.solution b/proj1-search-python3/test_cases/q6/medium_corners.solution new file mode 100644 index 0000000..913dc45 --- /dev/null +++ b/proj1-search-python3/test_cases/q6/medium_corners.solution @@ -0,0 +1,16 @@ +# This solution file specifies the length of the optimal path +# as well as the thresholds on number of nodes expanded to be +# used in scoring. +cost: "106" +path: """ +North East East East East North North West West West West North North +North North North North North North West West West West South South +East East East East South South South South South South West West +South South South West West East East North North North East East East +East East East East East South South East East East East East North +North East East North North East East North North East East East East +South South South South East East North North East East South South +South South South North North North North North North North West West +North North East East North North +""" +thresholds: "2000 1600 1200" diff --git a/proj1-search-python3/test_cases/q6/medium_corners.test b/proj1-search-python3/test_cases/q6/medium_corners.test new file mode 100644 index 0000000..dfa0a68 --- /dev/null +++ b/proj1-search-python3/test_cases/q6/medium_corners.test @@ -0,0 +1,19 @@ +class: "CornerHeuristicPacman" + +# The following specifies the layout to be used +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" diff --git a/proj1-search-python3/test_cases/q7/CONFIG b/proj1-search-python3/test_cases/q7/CONFIG new file mode 100644 index 0000000..ee85183 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/CONFIG @@ -0,0 +1,3 @@ +class: "PartialCreditQuestion" +max_points: "4" +depends: "q4" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_1.solution b/proj1-search-python3/test_cases/q7/food_heuristic_1.solution new file mode 100644 index 0000000..7a287f8 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_1.test. +solution_cost: "0" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_1.test b/proj1-search-python3/test_cases/q7/food_heuristic_1.test new file mode 100644 index 0000000..7545a7a --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_1.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 1" +layout: """ +%%%%%% +% % +% % +%P % +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_10.solution b/proj1-search-python3/test_cases/q7/food_heuristic_10.solution new file mode 100644 index 0000000..1917f05 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_10.test. +solution_cost: "7" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_10.test b/proj1-search-python3/test_cases/q7/food_heuristic_10.test new file mode 100644 index 0000000..212c7bd --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_10.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 10" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_11.solution b/proj1-search-python3/test_cases/q7/food_heuristic_11.solution new file mode 100644 index 0000000..11c3289 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_11.test. +solution_cost: "8" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_11.test b/proj1-search-python3/test_cases/q7/food_heuristic_11.test new file mode 100644 index 0000000..f5e6ed4 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_11.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 11" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_12.solution b/proj1-search-python3/test_cases/q7/food_heuristic_12.solution new file mode 100644 index 0000000..0edcc02 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_12.test. +solution_cost: "1" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_12.test b/proj1-search-python3/test_cases/q7/food_heuristic_12.test new file mode 100644 index 0000000..cc99a25 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_12.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 12" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_13.solution b/proj1-search-python3/test_cases/q7/food_heuristic_13.solution new file mode 100644 index 0000000..c25d50b --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_13.test. +solution_cost: "5" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_13.test b/proj1-search-python3/test_cases/q7/food_heuristic_13.test new file mode 100644 index 0000000..09d6f1e --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_13.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 13" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_14.solution b/proj1-search-python3/test_cases/q7/food_heuristic_14.solution new file mode 100644 index 0000000..e6cc475 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_14.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_14.test. +solution_cost: "31" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_14.test b/proj1-search-python3/test_cases/q7/food_heuristic_14.test new file mode 100644 index 0000000..58982e3 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_14.test @@ -0,0 +1,19 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 14" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_15.solution b/proj1-search-python3/test_cases/q7/food_heuristic_15.solution new file mode 100644 index 0000000..4eca0f1 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_15.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_15.test. +solution_cost: "21" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_15.test b/proj1-search-python3/test_cases/q7/food_heuristic_15.test new file mode 100644 index 0000000..df605c1 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_15.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 15" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_16.solution b/proj1-search-python3/test_cases/q7/food_heuristic_16.solution new file mode 100644 index 0000000..8d89992 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_16.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_16.test. +solution_cost: "7" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_16.test b/proj1-search-python3/test_cases/q7/food_heuristic_16.test new file mode 100644 index 0000000..762b433 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_16.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 16" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_17.solution b/proj1-search-python3/test_cases/q7/food_heuristic_17.solution new file mode 100644 index 0000000..63a9a1b --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_17.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_17.test. +solution_cost: "16" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_17.test b/proj1-search-python3/test_cases/q7/food_heuristic_17.test new file mode 100644 index 0000000..a923f67 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_17.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 17" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_2.solution b/proj1-search-python3/test_cases/q7/food_heuristic_2.solution new file mode 100644 index 0000000..ca5aba1 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_2.test. +solution_cost: "0" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_2.test b/proj1-search-python3/test_cases/q7/food_heuristic_2.test new file mode 100644 index 0000000..956e75d --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_2.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 2" +layout: """ +%%% +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +%P% +% % +% % +% % +% % +% % +%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_3.solution b/proj1-search-python3/test_cases/q7/food_heuristic_3.solution new file mode 100644 index 0000000..d1694b5 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_3.test. +solution_cost: "0" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_3.test b/proj1-search-python3/test_cases/q7/food_heuristic_3.test new file mode 100644 index 0000000..250a8b1 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_3.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 3" +layout: """ +%%%% +% % +% % +%P % +% % +% % +%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_4.solution b/proj1-search-python3/test_cases/q7/food_heuristic_4.solution new file mode 100644 index 0000000..6e1e82a --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_4.test. +solution_cost: "0" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_4.test b/proj1-search-python3/test_cases/q7/food_heuristic_4.test new file mode 100644 index 0000000..ed86a0c --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_4.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 4" +layout: """ +%%%%%%%% +% % % +% % %% % +% %P%% % +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_5.solution b/proj1-search-python3/test_cases/q7/food_heuristic_5.solution new file mode 100644 index 0000000..779e9e6 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_5.test. +solution_cost: "11" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_5.test b/proj1-search-python3/test_cases/q7/food_heuristic_5.test new file mode 100644 index 0000000..1f44c48 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_5.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 5" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_6.solution b/proj1-search-python3/test_cases/q7/food_heuristic_6.solution new file mode 100644 index 0000000..906b510 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_6.test. +solution_cost: "5" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_6.test b/proj1-search-python3/test_cases/q7/food_heuristic_6.test new file mode 100644 index 0000000..01d7f32 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_6.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 6" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_7.solution b/proj1-search-python3/test_cases/q7/food_heuristic_7.solution new file mode 100644 index 0000000..5994a7b --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_7.test. +solution_cost: "7" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_7.test b/proj1-search-python3/test_cases/q7/food_heuristic_7.test new file mode 100644 index 0000000..b1db372 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_7.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 7" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_8.solution b/proj1-search-python3/test_cases/q7/food_heuristic_8.solution new file mode 100644 index 0000000..0e4fb08 --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_8.test. +solution_cost: "5" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_8.test b/proj1-search-python3/test_cases/q7/food_heuristic_8.test new file mode 100644 index 0000000..b9430af --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_8.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 8" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_9.solution b/proj1-search-python3/test_cases/q7/food_heuristic_9.solution new file mode 100644 index 0000000..1470d9a --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_9.test. +solution_cost: "6" diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_9.test b/proj1-search-python3/test_cases/q7/food_heuristic_9.test new file mode 100644 index 0000000..799b41d --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_9.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 9" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.solution b/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.solution new file mode 100644 index 0000000..cd6fd7d --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_grade_tricky.test. +# File intentionally blank. diff --git a/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.test b/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.test new file mode 100644 index 0000000..081fb0d --- /dev/null +++ b/proj1-search-python3/test_cases/q7/food_heuristic_grade_tricky.test @@ -0,0 +1,19 @@ +class: "HeuristicGrade" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "trickySearch" +layout: """ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% +""" +# One point always, an extra point for each +# threshold passed. +basePoints: "1" +gradingThresholds: "15000 12000 9000 7000" + diff --git a/proj1-search-python3/test_cases/q8/CONFIG b/proj1-search-python3/test_cases/q8/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/proj1-search-python3/test_cases/q8/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/proj1-search-python3/test_cases/q8/closest_dot_1.solution b/proj1-search-python3/test_cases/q8/closest_dot_1.solution new file mode 100644 index 0000000..300fc25 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_1.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_1.test b/proj1-search-python3/test_cases/q8/closest_dot_1.test new file mode 100644 index 0000000..672989f --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_1.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 1" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_10.solution b/proj1-search-python3/test_cases/q8/closest_dot_10.solution new file mode 100644 index 0000000..174b5dd --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_10.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_10.test b/proj1-search-python3/test_cases/q8/closest_dot_10.test new file mode 100644 index 0000000..b1e0f33 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_10.test @@ -0,0 +1,17 @@ +class: "ClosestDotTest" + +layoutName: "Test 10" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_11.solution b/proj1-search-python3/test_cases/q8/closest_dot_11.solution new file mode 100644 index 0000000..80bbe38 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_11.test. +solution_length: "2" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_11.test b/proj1-search-python3/test_cases/q8/closest_dot_11.test new file mode 100644 index 0000000..0310a1e --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_11.test @@ -0,0 +1,30 @@ +class: "ClosestDotTest" + +layoutName: "Test 11" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_12.solution b/proj1-search-python3/test_cases/q8/closest_dot_12.solution new file mode 100644 index 0000000..6f38bcb --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_12.test. +solution_length: "3" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_12.test b/proj1-search-python3/test_cases/q8/closest_dot_12.test new file mode 100644 index 0000000..a17b628 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_12.test @@ -0,0 +1,13 @@ +class: "ClosestDotTest" + +layoutName: "Test 12" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_13.solution b/proj1-search-python3/test_cases/q8/closest_dot_13.solution new file mode 100644 index 0000000..7afa908 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_13.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_13.test b/proj1-search-python3/test_cases/q8/closest_dot_13.test new file mode 100644 index 0000000..87c423d --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_13.test @@ -0,0 +1,12 @@ +class: "ClosestDotTest" + +layoutName: "Test 13" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_2.solution b/proj1-search-python3/test_cases/q8/closest_dot_2.solution new file mode 100644 index 0000000..16d75de --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_2.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_2.test b/proj1-search-python3/test_cases/q8/closest_dot_2.test new file mode 100644 index 0000000..4b59602 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_2.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 2" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_3.solution b/proj1-search-python3/test_cases/q8/closest_dot_3.solution new file mode 100644 index 0000000..cbd5974 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_3.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_3.test b/proj1-search-python3/test_cases/q8/closest_dot_3.test new file mode 100644 index 0000000..aa2a3af --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_3.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 3" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_4.solution b/proj1-search-python3/test_cases/q8/closest_dot_4.solution new file mode 100644 index 0000000..ca520b5 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_4.test. +solution_length: "3" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_4.test b/proj1-search-python3/test_cases/q8/closest_dot_4.test new file mode 100644 index 0000000..8499f6d --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_4.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 4" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_5.solution b/proj1-search-python3/test_cases/q8/closest_dot_5.solution new file mode 100644 index 0000000..5c526a2 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_5.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_5.test b/proj1-search-python3/test_cases/q8/closest_dot_5.test new file mode 100644 index 0000000..dfaee3d --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_5.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 5" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_6.solution b/proj1-search-python3/test_cases/q8/closest_dot_6.solution new file mode 100644 index 0000000..b06468a --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_6.test. +solution_length: "2" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_6.test b/proj1-search-python3/test_cases/q8/closest_dot_6.test new file mode 100644 index 0000000..bc50c57 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_6.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 6" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_7.solution b/proj1-search-python3/test_cases/q8/closest_dot_7.solution new file mode 100644 index 0000000..3231b28 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_7.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_7.test b/proj1-search-python3/test_cases/q8/closest_dot_7.test new file mode 100644 index 0000000..746e89a --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_7.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 7" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_8.solution b/proj1-search-python3/test_cases/q8/closest_dot_8.solution new file mode 100644 index 0000000..646e621 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_8.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_8.test b/proj1-search-python3/test_cases/q8/closest_dot_8.test new file mode 100644 index 0000000..c266ae1 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_8.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 8" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/test_cases/q8/closest_dot_9.solution b/proj1-search-python3/test_cases/q8/closest_dot_9.solution new file mode 100644 index 0000000..6c94aa5 --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_9.test. +solution_length: "1" diff --git a/proj1-search-python3/test_cases/q8/closest_dot_9.test b/proj1-search-python3/test_cases/q8/closest_dot_9.test new file mode 100644 index 0000000..da078de --- /dev/null +++ b/proj1-search-python3/test_cases/q8/closest_dot_9.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 9" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/proj1-search-python3/textDisplay.py b/proj1-search-python3/textDisplay.py new file mode 100644 index 0000000..8e25a46 --- /dev/null +++ b/proj1-search-python3/textDisplay.py @@ -0,0 +1,81 @@ +# textDisplay.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import time +try: + import pacman +except: + pass + +DRAW_EVERY = 1 +SLEEP_TIME = 0 # This can be overwritten by __init__ +DISPLAY_MOVES = False +QUIET = False # Supresses output + +class NullGraphics: + def initialize(self, state, isBlue = False): + pass + + def update(self, state): + pass + + def checkNullDisplay(self): + return True + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def updateDistributions(self, dist): + pass + + def finish(self): + pass + +class PacmanGraphics: + def __init__(self, speed=None): + if speed != None: + global SLEEP_TIME + SLEEP_TIME = speed + + def initialize(self, state, isBlue = False): + self.draw(state) + self.pause() + self.turn = 0 + self.agentCounter = 0 + + def update(self, state): + numAgents = len(state.agentStates) + self.agentCounter = (self.agentCounter + 1) % numAgents + if self.agentCounter == 0: + self.turn += 1 + if DISPLAY_MOVES: + ghosts = [pacman.nearestPoint(state.getGhostPosition(i)) for i in range(1, numAgents)] + print("%4d) P: %-8s" % (self.turn, str(pacman.nearestPoint(state.getPacmanPosition()))),'| Score: %-5d' % state.score,'| Ghosts:', ghosts) + if self.turn % DRAW_EVERY == 0: + self.draw(state) + self.pause() + if state._win or state._lose: + self.draw(state) + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def finish(self): + pass diff --git a/proj1-search-python3/util.py b/proj1-search-python3/util.py new file mode 100644 index 0000000..d81667f --- /dev/null +++ b/proj1-search-python3/util.py @@ -0,0 +1,672 @@ +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import inspect +import heapq, random + + +class FixedRandom: + def __init__(self): + fixedState = (3, (2147483648, 507801126, 683453281, 310439348, 2597246090, \ + 2209084787, 2267831527, 979920060, 3098657677, 37650879, 807947081, 3974896263, \ + 881243242, 3100634921, 1334775171, 3965168385, 746264660, 4074750168, 500078808, \ + 776561771, 702988163, 1636311725, 2559226045, 157578202, 2498342920, 2794591496, \ + 4130598723, 496985844, 2944563015, 3731321600, 3514814613, 3362575829, 3038768745, \ + 2206497038, 1108748846, 1317460727, 3134077628, 988312410, 1674063516, 746456451, \ + 3958482413, 1857117812, 708750586, 1583423339, 3466495450, 1536929345, 1137240525, \ + 3875025632, 2466137587, 1235845595, 4214575620, 3792516855, 657994358, 1241843248, \ + 1695651859, 3678946666, 1929922113, 2351044952, 2317810202, 2039319015, 460787996, \ + 3654096216, 4068721415, 1814163703, 2904112444, 1386111013, 574629867, 2654529343, \ + 3833135042, 2725328455, 552431551, 4006991378, 1331562057, 3710134542, 303171486, \ + 1203231078, 2670768975, 54570816, 2679609001, 578983064, 1271454725, 3230871056, \ + 2496832891, 2944938195, 1608828728, 367886575, 2544708204, 103775539, 1912402393, \ + 1098482180, 2738577070, 3091646463, 1505274463, 2079416566, 659100352, 839995305, \ + 1696257633, 274389836, 3973303017, 671127655, 1061109122, 517486945, 1379749962, \ + 3421383928, 3116950429, 2165882425, 2346928266, 2892678711, 2936066049, 1316407868, \ + 2873411858, 4279682888, 2744351923, 3290373816, 1014377279, 955200944, 4220990860, \ + 2386098930, 1772997650, 3757346974, 1621616438, 2877097197, 442116595, 2010480266, \ + 2867861469, 2955352695, 605335967, 2222936009, 2067554933, 4129906358, 1519608541, \ + 1195006590, 1942991038, 2736562236, 279162408, 1415982909, 4099901426, 1732201505, \ + 2934657937, 860563237, 2479235483, 3081651097, 2244720867, 3112631622, 1636991639, \ + 3860393305, 2312061927, 48780114, 1149090394, 2643246550, 1764050647, 3836789087, \ + 3474859076, 4237194338, 1735191073, 2150369208, 92164394, 756974036, 2314453957, \ + 323969533, 4267621035, 283649842, 810004843, 727855536, 1757827251, 3334960421, \ + 3261035106, 38417393, 2660980472, 1256633965, 2184045390, 811213141, 2857482069, \ + 2237770878, 3891003138, 2787806886, 2435192790, 2249324662, 3507764896, 995388363, \ + 856944153, 619213904, 3233967826, 3703465555, 3286531781, 3863193356, 2992340714, \ + 413696855, 3865185632, 1704163171, 3043634452, 2225424707, 2199018022, 3506117517, \ + 3311559776, 3374443561, 1207829628, 668793165, 1822020716, 2082656160, 1160606415, \ + 3034757648, 741703672, 3094328738, 459332691, 2702383376, 1610239915, 4162939394, \ + 557861574, 3805706338, 3832520705, 1248934879, 3250424034, 892335058, 74323433, \ + 3209751608, 3213220797, 3444035873, 3743886725, 1783837251, 610968664, 580745246, \ + 4041979504, 201684874, 2673219253, 1377283008, 3497299167, 2344209394, 2304982920, \ + 3081403782, 2599256854, 3184475235, 3373055826, 695186388, 2423332338, 222864327, \ + 1258227992, 3627871647, 3487724980, 4027953808, 3053320360, 533627073, 3026232514, \ + 2340271949, 867277230, 868513116, 2158535651, 2487822909, 3428235761, 3067196046, \ + 3435119657, 1908441839, 788668797, 3367703138, 3317763187, 908264443, 2252100381, \ + 764223334, 4127108988, 384641349, 3377374722, 1263833251, 1958694944, 3847832657, \ + 1253909612, 1096494446, 555725445, 2277045895, 3340096504, 1383318686, 4234428127, \ + 1072582179, 94169494, 1064509968, 2681151917, 2681864920, 734708852, 1338914021, \ + 1270409500, 1789469116, 4191988204, 1716329784, 2213764829, 3712538840, 919910444, \ + 1318414447, 3383806712, 3054941722, 3378649942, 1205735655, 1268136494, 2214009444, \ + 2532395133, 3232230447, 230294038, 342599089, 772808141, 4096882234, 3146662953, \ + 2784264306, 1860954704, 2675279609, 2984212876, 2466966981, 2627986059, 2985545332, \ + 2578042598, 1458940786, 2944243755, 3959506256, 1509151382, 325761900, 942251521, \ + 4184289782, 2756231555, 3297811774, 1169708099, 3280524138, 3805245319, 3227360276, \ + 3199632491, 2235795585, 2865407118, 36763651, 2441503575, 3314890374, 1755526087, \ + 17915536, 1196948233, 949343045, 3815841867, 489007833, 2654997597, 2834744136, \ + 417688687, 2843220846, 85621843, 747339336, 2043645709, 3520444394, 1825470818, \ + 647778910, 275904777, 1249389189, 3640887431, 4200779599, 323384601, 3446088641, \ + 4049835786, 1718989062, 3563787136, 44099190, 3281263107, 22910812, 1826109246, \ + 745118154, 3392171319, 1571490704, 354891067, 815955642, 1453450421, 940015623, \ + 796817754, 1260148619, 3898237757, 176670141, 1870249326, 3317738680, 448918002, \ + 4059166594, 2003827551, 987091377, 224855998, 3520570137, 789522610, 2604445123, \ + 454472869, 475688926, 2990723466, 523362238, 3897608102, 806637149, 2642229586, \ + 2928614432, 1564415411, 1691381054, 3816907227, 4082581003, 1895544448, 3728217394, \ + 3214813157, 4054301607, 1882632454, 2873728645, 3694943071, 1297991732, 2101682438, \ + 3952579552, 678650400, 1391722293, 478833748, 2976468591, 158586606, 2576499787, \ + 662690848, 3799889765, 3328894692, 2474578497, 2383901391, 1718193504, 3003184595, \ + 3630561213, 1929441113, 3848238627, 1594310094, 3040359840, 3051803867, 2462788790, \ + 954409915, 802581771, 681703307, 545982392, 2738993819, 8025358, 2827719383, \ + 770471093, 3484895980, 3111306320, 3900000891, 2116916652, 397746721, 2087689510, \ + 721433935, 1396088885, 2751612384, 1998988613, 2135074843, 2521131298, 707009172, \ + 2398321482, 688041159, 2264560137, 482388305, 207864885, 3735036991, 3490348331, \ + 1963642811, 3260224305, 3493564223, 1939428454, 1128799656, 1366012432, 2858822447, \ + 1428147157, 2261125391, 1611208390, 1134826333, 2374102525, 3833625209, 2266397263, \ + 3189115077, 770080230, 2674657172, 4280146640, 3604531615, 4235071805, 3436987249, \ + 509704467, 2582695198, 4256268040, 3391197562, 1460642842, 1617931012, 457825497, \ + 1031452907, 1330422862, 4125947620, 2280712485, 431892090, 2387410588, 2061126784, \ + 896457479, 3480499461, 2488196663, 4021103792, 1877063114, 2744470201, 1046140599, \ + 2129952955, 3583049218, 4217723693, 2720341743, 820661843, 1079873609, 3360954200, \ + 3652304997, 3335838575, 2178810636, 1908053374, 4026721976, 1793145418, 476541615, \ + 973420250, 515553040, 919292001, 2601786155, 1685119450, 3030170809, 1590676150, \ + 1665099167, 651151584, 2077190587, 957892642, 646336572, 2743719258, 866169074, \ + 851118829, 4225766285, 963748226, 799549420, 1955032629, 799460000, 2425744063, \ + 2441291571, 1928963772, 528930629, 2591962884, 3495142819, 1896021824, 901320159, \ + 3181820243, 843061941, 3338628510, 3782438992, 9515330, 1705797226, 953535929, \ + 764833876, 3202464965, 2970244591, 519154982, 3390617541, 566616744, 3438031503, \ + 1853838297, 170608755, 1393728434, 676900116, 3184965776, 1843100290, 78995357, \ + 2227939888, 3460264600, 1745705055, 1474086965, 572796246, 4081303004, 882828851, \ + 1295445825, 137639900, 3304579600, 2722437017, 4093422709, 273203373, 2666507854, \ + 3998836510, 493829981, 1623949669, 3482036755, 3390023939, 833233937, 1639668730, \ + 1499455075, 249728260, 1210694006, 3836497489, 1551488720, 3253074267, 3388238003, \ + 2372035079, 3945715164, 2029501215, 3362012634, 2007375355, 4074709820, 631485888, \ + 3135015769, 4273087084, 3648076204, 2739943601, 1374020358, 1760722448, 3773939706, \ + 1313027823, 1895251226, 4224465911, 421382535, 1141067370, 3660034846, 3393185650, \ + 1850995280, 1451917312, 3841455409, 3926840308, 1397397252, 2572864479, 2500171350, \ + 3119920613, 531400869, 1626487579, 1099320497, 407414753, 2438623324, 99073255, \ + 3175491512, 656431560, 1153671785, 236307875, 2824738046, 2320621382, 892174056, \ + 230984053, 719791226, 2718891946, 624), None) + self.random = random.Random() + self.random.setstate(fixedState) + +""" + Data structures useful for implementing SearchAgents +""" + +class Stack: + "A container with a last-in-first-out (LIFO) queuing policy." + def __init__(self): + self.list = [] + + def push(self,item): + "Push 'item' onto the stack" + self.list.append(item) + + def pop(self): + "Pop the most recently pushed item from the stack" + return self.list.pop() + + def isEmpty(self): + "Returns true if the stack is empty" + return len(self.list) == 0 + +class Queue: + "A container with a first-in-first-out (FIFO) queuing policy." + def __init__(self): + self.list = [] + + def push(self,item): + "Enqueue the 'item' into the queue" + self.list.insert(0,item) + + def pop(self): + """ + Dequeue the earliest enqueued item still in the queue. This + operation removes the item from the queue. + """ + return self.list.pop() + + def isEmpty(self): + "Returns true if the queue is empty" + return len(self.list) == 0 + +class PriorityQueue: + """ + Implements a priority queue data structure. Each inserted item + has a priority associated with it and the client is usually interested + in quick retrieval of the lowest-priority item in the queue. This + data structure allows O(1) access to the lowest-priority item. + """ + def __init__(self): + self.heap = [] + self.count = 0 + + def push(self, item, priority): + entry = (priority, self.count, item) + heapq.heappush(self.heap, entry) + self.count += 1 + + def pop(self): + (_, _, item) = heapq.heappop(self.heap) + return item + + def isEmpty(self): + return len(self.heap) == 0 + + def update(self, item, priority): + # If item already in priority queue with higher priority, update its priority and rebuild the heap. + # If item already in priority queue with equal or lower priority, do nothing. + # If item not in priority queue, do the same thing as self.push. + for index, (p, c, i) in enumerate(self.heap): + if i == item: + if p <= priority: + break + del self.heap[index] + self.heap.append((priority, c, item)) + heapq.heapify(self.heap) + break + else: + self.push(item, priority) + +class PriorityQueueWithFunction(PriorityQueue): + """ + Implements a priority queue with the same push/pop signature of the + Queue and the Stack classes. This is designed for drop-in replacement for + those two classes. The caller has to provide a priority function, which + extracts each item's priority. + """ + def __init__(self, priorityFunction): + "priorityFunction (item) -> priority" + self.priorityFunction = priorityFunction # store the priority function + PriorityQueue.__init__(self) # super-class initializer + + def push(self, item): + "Adds an item to the queue with priority from the priority function" + PriorityQueue.push(self, item, self.priorityFunction(item)) + + +def manhattanDistance( xy1, xy2 ): + "Returns the Manhattan distance between points xy1 and xy2" + return abs( xy1[0] - xy2[0] ) + abs( xy1[1] - xy2[1] ) + +""" + Data structures and functions useful for various course projects + + The search project should not need anything below this line. +""" + +class Counter(dict): + """ + A counter keeps track of counts for a set of keys. + + The counter class is an extension of the standard python + dictionary type. It is specialized to have number values + (integers or floats), and includes a handful of additional + functions to ease the task of counting data. In particular, + all keys are defaulted to have value 0. Using a dictionary: + + a = {} + print(a['test']) + + would give an error, while the Counter class analogue: + + >>> a = Counter() + >>> print(a['test']) + 0 + + returns the default 0 value. Note that to reference a key + that you know is contained in the counter, + you can still use the dictionary syntax: + + >>> a = Counter() + >>> a['test'] = 2 + >>> print(a['test']) + 2 + + This is very useful for counting things without initializing their counts, + see for example: + + >>> a['blah'] += 1 + >>> print(a['blah']) + 1 + + The counter also includes additional functionality useful in implementing + the classifiers for this assignment. Two counters can be added, + subtracted or multiplied together. See below for details. They can + also be normalized and their total count and arg max can be extracted. + """ + def __getitem__(self, idx): + self.setdefault(idx, 0) + return dict.__getitem__(self, idx) + + def incrementAll(self, keys, count): + """ + Increments all elements of keys by the same count. + + >>> a = Counter() + >>> a.incrementAll(['one','two', 'three'], 1) + >>> a['one'] + 1 + >>> a['two'] + 1 + """ + for key in keys: + self[key] += count + + def argMax(self): + """ + Returns the key with the highest value. + """ + if len(self.keys()) == 0: return None + all = self.items() + values = [x[1] for x in all] + maxIndex = values.index(max(values)) + return all[maxIndex][0] + + def sortedKeys(self): + """ + Returns a list of keys sorted by their values. Keys + with the highest values will appear first. + + >>> a = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> a['third'] = 1 + >>> a.sortedKeys() + ['second', 'third', 'first'] + """ + sortedItems = self.items() + compare = lambda x, y: sign(y[1] - x[1]) + sortedItems.sort(cmp=compare) + return [x[0] for x in sortedItems] + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + + def normalize(self): + """ + Edits the counter such that the total count of all + keys sums to 1. The ratio of counts for all keys + will remain the same. Note that normalizing an empty + Counter will result in an error. + """ + total = float(self.totalCount()) + if total == 0: return + for key in self.keys(): + self[key] = self[key] / total + + def divideAll(self, divisor): + """ + Divides all counts by divisor + """ + divisor = float(divisor) + for key in self: + self[key] /= divisor + + def copy(self): + """ + Returns a copy of the counter + """ + return Counter(dict.copy(self)) + + def __mul__(self, y ): + """ + Multiplying two counters gives the dot product of their vectors where + each unique label is a vector element. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['second'] = 5 + >>> a['third'] = 1.5 + >>> a['fourth'] = 2.5 + >>> a * b + 14 + """ + sum = 0 + x = self + if len(x) > len(y): + x,y = y,x + for key in x: + if key not in y: + continue + sum += x[key] * y[key] + return sum + + def __radd__(self, y): + """ + Adding another counter to a counter increments the current counter + by the values stored in the second counter. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> a += b + >>> a['first'] + 1 + """ + for key, value in y.items(): + self[key] += value + + def __add__( self, y ): + """ + Adding two counters gives a counter with the union of all keys and + counts of the second added to counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a + b)['first'] + 1 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] + y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = y[key] + return addend + + def __sub__( self, y ): + """ + Subtracting a counter from another gives a counter with the union of all keys and + counts of the second subtracted from counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a - b)['first'] + -5 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] - y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = -1 * y[key] + return addend + +def raiseNotDefined(): + fileName = inspect.stack()[1][1] + line = inspect.stack()[1][2] + method = inspect.stack()[1][3] + + print("*** Method not implemented: %s at line %s of %s" % (method, line, fileName)) + sys.exit(1) + +def normalize(vectorOrCounter): + """ + normalize a vector or counter by dividing each value by the sum of all values + """ + normalizedCounter = Counter() + if type(vectorOrCounter) == type(normalizedCounter): + counter = vectorOrCounter + total = float(counter.totalCount()) + if total == 0: return counter + for key in counter.keys(): + value = counter[key] + normalizedCounter[key] = value / total + return normalizedCounter + else: + vector = vectorOrCounter + s = float(sum(vector)) + if s == 0: return vector + return [el / s for el in vector] + +def nSample(distribution, values, n): + if sum(distribution) != 1: + distribution = normalize(distribution) + rand = [random.random() for i in range(n)] + rand.sort() + samples = [] + samplePos, distPos, cdf = 0,0, distribution[0] + while samplePos < n: + if rand[samplePos] < cdf: + samplePos += 1 + samples.append(values[distPos]) + else: + distPos += 1 + cdf += distribution[distPos] + return samples + +def sample(distribution, values = None): + if type(distribution) == Counter: + items = sorted(distribution.items()) + distribution = [i[1] for i in items] + values = [i[0] for i in items] + if sum(distribution) != 1: + distribution = normalize(distribution) + choice = random.random() + i, total= 0, distribution[0] + while choice > total: + i += 1 + total += distribution[i] + return values[i] + +def sampleFromCounter(ctr): + items = sorted(ctr.items()) + return sample([v for k,v in items], [k for k,v in items]) + +def getProbability(value, distribution, values): + """ + Gives the probability of a value under a discrete distribution + defined by (distributions, values). + """ + total = 0.0 + for prob, val in zip(distribution, values): + if val == value: + total += prob + return total + +def flipCoin( p ): + r = random.random() + return r < p + +def chooseFromDistribution( distribution ): + "Takes either a counter or a list of (prob, key) pairs and samples" + if type(distribution) == dict or type(distribution) == Counter: + return sample(distribution) + r = random.random() + base = 0.0 + for prob, element in distribution: + base += prob + if r <= base: return element + +def nearestPoint( pos ): + """ + Finds the nearest grid point to a position (discretizes). + """ + ( current_row, current_col ) = pos + + grid_row = int( current_row + 0.5 ) + grid_col = int( current_col + 0.5 ) + return ( grid_row, grid_col ) + +def sign( x ): + """ + Returns 1 or -1 depending on the sign of x + """ + if( x >= 0 ): + return 1 + else: + return -1 + +def arrayInvert(array): + """ + Inverts a matrix stored as a list of lists. + """ + result = [[] for i in array] + for outer in array: + for inner in range(len(outer)): + result[inner].append(outer[inner]) + return result + +def matrixAsList( matrix, value = True ): + """ + Turns a matrix into a list of coordinates matching the specified value + """ + rows, cols = len( matrix ), len( matrix[0] ) + cells = [] + for row in range( rows ): + for col in range( cols ): + if matrix[row][col] == value: + cells.append( ( row, col ) ) + return cells + +def lookup(name, namespace): + """ + Get a method or class from any imported module from its name. + Usage: lookup(functionName, globals()) + """ + dots = name.count('.') + if dots > 0: + moduleName, objName = '.'.join(name.split('.')[:-1]), name.split('.')[-1] + module = __import__(moduleName) + return getattr(module, objName) + else: + modules = [obj for obj in namespace.values() if str(type(obj)) == ""] + options = [getattr(module, name) for module in modules if name in dir(module)] + options += [obj[1] for obj in namespace.items() if obj[0] == name ] + if len(options) == 1: return options[0] + if len(options) > 1: raise Exception('Name conflict for %s') + raise Exception('%s not found as a method or class' % name) + +def pause(): + """ + Pauses the output stream awaiting user feedback. + """ + input("") + + +# code to handle timeouts +# +# FIXME +# NOTE: TimeoutFuncton is NOT reentrant. Later timeouts will silently +# disable earlier timeouts. Could be solved by maintaining a global list +# of active time outs. Currently, questions which have test cases calling +# this have all student code so wrapped. +# +import signal +import time +class TimeoutFunctionException(Exception): + """Exception to raise on a timeout""" + pass + + +class TimeoutFunction: + def __init__(self, function, timeout): + self.timeout = timeout + self.function = function + + def handle_timeout(self, signum, frame): + raise TimeoutFunctionException() + + def __call__(self, *args, **keyArgs): + # If we have SIGALRM signal, use it to cause an exception if and + # when this function runs too long. Otherwise check the time taken + # after the method has returned, and throw an exception then. + if hasattr(signal, 'SIGALRM'): + old = signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.timeout) + try: + result = self.function(*args, **keyArgs) + finally: + signal.signal(signal.SIGALRM, old) + signal.alarm(0) + else: + startTime = time.time() + result = self.function(*args, **keyArgs) + timeElapsed = time.time() - startTime + if timeElapsed >= self.timeout: + self.handle_timeout(None, None) + return result + + + +_ORIGINAL_STDOUT = None +_ORIGINAL_STDERR = None +_MUTED = False + +class WritableNull: + def write(self, string): + pass + +def mutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if _MUTED: + return + _MUTED = True + + _ORIGINAL_STDOUT = sys.stdout + #_ORIGINAL_STDERR = sys.stderr + sys.stdout = WritableNull() + #sys.stderr = WritableNull() + +def unmutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if not _MUTED: + return + _MUTED = False + + sys.stdout = _ORIGINAL_STDOUT + #sys.stderr = _ORIGINAL_STDERR + diff --git a/search/VERSION b/search/VERSION new file mode 100644 index 0000000..7c7fa2f --- /dev/null +++ b/search/VERSION @@ -0,0 +1 @@ +v1.001 diff --git a/search/autograder.py b/search/autograder.py new file mode 100644 index 0000000..770a530 --- /dev/null +++ b/search/autograder.py @@ -0,0 +1,358 @@ +# autograder.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# imports from python standard library +import grading +import imp +import optparse +import os +import re +import sys +import projectParams +import random +random.seed(0) +try: + from pacman import GameState +except: + pass + +# register arguments and set default values +def readCommand(argv): + parser = optparse.OptionParser(description = 'Run public tests on student code') + parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, muteOutput=False, printTestCase=False, noGraphics=False) + parser.add_option('--test-directory', + dest = 'testRoot', + default = 'test_cases', + help = 'Root test directory which contains subdirectories corresponding to each question') + parser.add_option('--student-code', + dest = 'studentCode', + default = projectParams.STUDENT_CODE_DEFAULT, + help = 'comma separated list of student code files') + parser.add_option('--code-directory', + dest = 'codeRoot', + default = "", + help = 'Root directory containing the student and testClass code') + parser.add_option('--test-case-code', + dest = 'testCaseCode', + default = projectParams.PROJECT_TEST_CLASSES, + help = 'class containing testClass classes for this project') + parser.add_option('--generate-solutions', + dest = 'generateSolutions', + action = 'store_true', + help = 'Write solutions generated to .solution file') + parser.add_option('--edx-output', + dest = 'edxOutput', + action = 'store_true', + help = 'Generate edX output files') + parser.add_option('--gradescope-output', + dest = 'gsOutput', + action = 'store_true', + help = 'Generate GradeScope output files') + parser.add_option('--mute', + dest = 'muteOutput', + action = 'store_true', + help = 'Mute output from executing tests') + parser.add_option('--print-tests', '-p', + dest = 'printTestCase', + action = 'store_true', + help = 'Print each test case before running them.') + parser.add_option('--test', '-t', + dest = 'runTest', + default = None, + help = 'Run one particular test. Relative to test root.') + parser.add_option('--question', '-q', + dest = 'gradeQuestion', + default = None, + help = 'Grade one particular question.') + parser.add_option('--no-graphics', + dest = 'noGraphics', + action = 'store_true', + help = 'No graphics display for pacman games.') + (options, args) = parser.parse_args(argv) + return options + + +# confirm we should author solution files +def confirmGenerate(): + print ('WARNING: this action will overwrite any solution files.') + print ('Are you sure you want to proceed? (yes/no)') + while True: + ans = sys.stdin.readline().strip() + if ans == 'yes': + break + elif ans == 'no': + sys.exit(0) + else: + print( 'please answer either "yes" or "no"') + + +# TODO: Fix this so that it tracebacks work correctly +# Looking at source of the traceback module, presuming it works +# the same as the intepreters, it uses co_filename. This is, +# however, a readonly attribute. +def setModuleName(module, filename): + functionType = type(confirmGenerate) + classType = type(optparse.Option) + + for i in dir(module): + o = getattr(module, i) + if hasattr(o, '__file__'): continue + + if type(o) == functionType: + setattr(o, '__file__', filename) + elif type(o) == classType: + setattr(o, '__file__', filename) + # TODO: assign member __file__'s? + #print i, type(o) + + +#from cStringIO import StringIO + +def loadModuleString(moduleSource): + # Below broken, imp doesn't believe its being passed a file: + # ValueError: load_module arg#2 should be a file or None + # + #f = StringIO(moduleCodeDict[k]) + #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE)) + tmp = imp.new_module(k) + exec moduleCodeDict[k] in tmp.__dict__ + setModuleName(tmp, k) + return tmp + +import py_compile + +def loadModuleFile(moduleName, filePath): + with open(filePath, 'r') as f: + return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE)) + + +def readFile(path, root=""): + "Read file from disk at specified path and return as string" + with open(os.path.join(root, path), 'r') as handle: + return handle.read() + + +####################################################################### +# Error Hint Map +####################################################################### + +# TODO: use these +ERROR_HINT_MAP = { + 'q1': { + "": """ + We noticed that your project threw an IndexError on q1. + While many things may cause this, it may have been from + assuming a certain number of successors from a state space + or assuming a certain number of actions available from a given + state. Try making your code more general (no hardcoded indices) + and submit again! + """ + }, + 'q3': { + "": """ + We noticed that your project threw an AttributeError on q3. + While many things may cause this, it may have been from assuming + a certain size or structure to the state space. For example, if you have + a line of code assuming that the state is (x, y) and we run your code + on a state space with (x, y, z), this error could be thrown. Try + making your code more general and submit again! + + """ + } +} + +import pprint + +def splitStrings(d): + d2 = dict(d) + for k in d: + if k[0:2] == "__": + del d2[k] + continue + if d2[k].find("\n") >= 0: + d2[k] = d2[k].split("\n") + return d2 + + +def printTest(testDict, solutionDict): + pp = pprint.PrettyPrinter(indent=4) + print "Test case:" + for line in testDict["__raw_lines__"]: + print " |", line + print "Solution:" + for line in solutionDict["__raw_lines__"]: + print " |", line + + +def runTest(testName, moduleDict, printTestCase=False, display=None): + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + testDict = testParser.TestParser(testName + ".test").parse() + solutionDict = testParser.TestParser(testName + ".solution").parse() + test_out_file = os.path.join('%s.test_output' % testName) + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + + questionClass = getattr(testClasses, 'Question') + question = questionClass({'max_points': 0}, display) + testCase = testClass(question, testDict) + + if printTestCase: + printTest(testDict, solutionDict) + + # This is a fragile hack to create a stub grades object + grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)]) + testCase.execute(grades, moduleDict, solutionDict) + + +# returns all the tests you need to run in order to run question +def getDepends(testParser, testRoot, question): + allDeps = [question] + questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse() + if 'depends' in questionDict: + depends = questionDict['depends'].split() + for d in depends: + # run dependencies first + allDeps = getDepends(testParser, testRoot, d) + allDeps + return allDeps + +# get list of questions to grade +def getTestSubdirs(testParser, testRoot, questionToGrade): + problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse() + if questionToGrade != None: + questions = getDepends(testParser, testRoot, questionToGrade) + if len(questions) > 1: + print 'Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions) + return questions + if 'order' in problemDict: + return problemDict['order'].split() + return sorted(os.listdir(testRoot)) + + +# evaluate student code +def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, + edxOutput=False, muteOutput=False, gsOutput=False, + printTestCase=False, questionToGrade=None, display=None): + # imports of testbench code. note that the testClasses import must follow + # the import of student code due to dependencies + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + questions = [] + questionDicts = {} + test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade) + for q in test_subdirs: + subdir_path = os.path.join(testRoot, q) + if not os.path.isdir(subdir_path) or q[0] == '.': + continue + + # create a question object + questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse() + questionClass = getattr(testClasses, questionDict['class']) + question = questionClass(questionDict, display) + questionDicts[q] = questionDict + + # load test cases into question + tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path)) + tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests) + for t in sorted(tests): + test_file = os.path.join(subdir_path, '%s.test' % t) + solution_file = os.path.join(subdir_path, '%s.solution' % t) + test_out_file = os.path.join(subdir_path, '%s.test_output' % t) + testDict = testParser.TestParser(test_file).parse() + if testDict.get("disabled", "false").lower() == "true": + continue + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + testCase = testClass(question, testDict) + def makefun(testCase, solution_file): + if generateSolutions: + # write solution file to disk + return lambda grades: testCase.writeSolution(moduleDict, solution_file) + else: + # read in solution dictionary and pass as an argument + testDict = testParser.TestParser(test_file).parse() + solutionDict = testParser.TestParser(solution_file).parse() + if printTestCase: + return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict) + else: + return lambda grades: testCase.execute(grades, moduleDict, solutionDict) + question.addTestCase(testCase, makefun(testCase, solution_file)) + + # Note extra function is necessary for scoping reasons + def makefun(question): + return lambda grades: question.execute(grades) + setattr(sys.modules[__name__], q, makefun(question)) + questions.append((q, question.getMaxPoints())) + + grades = grading.Grades(projectParams.PROJECT_NAME, questions, + gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput) + if questionToGrade == None: + for q in questionDicts: + for prereq in questionDicts[q].get('depends', '').split(): + grades.addPrereq(q, prereq) + + grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC) + return grades.points + + + +def getDisplay(graphicsByDefault, options=None): + graphics = graphicsByDefault + if options is not None and options.noGraphics: + graphics = False + if graphics: + try: + import graphicsDisplay + return graphicsDisplay.PacmanGraphics(1, frameTime=.05) + except ImportError: + pass + import textDisplay + return textDisplay.NullGraphics() + + + + +if __name__ == '__main__': + options = readCommand(sys.argv) + if options.generateSolutions: + confirmGenerate() + codePaths = options.studentCode.split(',') + # moduleCodeDict = {} + # for cp in codePaths: + # moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot) + # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot) + # moduleDict = loadModuleDict(moduleCodeDict) + + moduleDict = {} + for cp in codePaths: + moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp)) + moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1) + moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode)) + + + if options.runTest != None: + runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options)) + else: + evaluate(options.generateSolutions, options.testRoot, moduleDict, + gsOutput=options.gsOutput, + edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase, + questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options)) diff --git a/search/commands.txt b/search/commands.txt new file mode 100644 index 0000000..d5c70e2 --- /dev/null +++ b/search/commands.txt @@ -0,0 +1,22 @@ +python pacman.py +python pacman.py --layout testMaze --pacman GoWestAgent +python pacman.py --layout tinyMaze --pacman GoWestAgent +python pacman.py -h +python pacman.py -l tinyMaze -p SearchAgent -a fn=tinyMazeSearch +python pacman.py -l tinyMaze -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent -a fn=bfs +python pacman.py -l bigMaze -p SearchAgent -a fn=bfs -z .5 +python eightpuzzle.py +python pacman.py -l mediumMaze -p SearchAgent -a fn=ucs +python pacman.py -l mediumDottedMaze -p StayEastSearchAgent +python pacman.py -l mediumScaryMaze -p StayWestSearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent -a fn=astar,heuristic=manhattanHeuristic +python pacman.py -l tinyCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5 +python pacman.py -l testSearch -p AStarFoodSearchAgent +python pacman.py -l trickySearch -p AStarFoodSearchAgent +python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5 +python pacman.py -l bigSearch -p ApproximateSearchAgent -z .5 -q diff --git a/search/eightpuzzle.py b/search/eightpuzzle.py new file mode 100644 index 0000000..6aa376c --- /dev/null +++ b/search/eightpuzzle.py @@ -0,0 +1,281 @@ +# eightpuzzle.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import search +import random + +# Module Classes + +class EightPuzzleState: + """ + The Eight Puzzle is described in the course textbook on + page 64. + + This class defines the mechanics of the puzzle itself. The + task of recasting this puzzle as a search problem is left to + the EightPuzzleSearchProblem class. + """ + + def __init__( self, numbers ): + """ + Constructs a new eight puzzle from an ordering of numbers. + + numbers: a list of integers from 0 to 8 representing an + instance of the eight puzzle. 0 represents the blank + space. Thus, the list + + [1, 0, 2, 3, 4, 5, 6, 7, 8] + + represents the eight puzzle: + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------ + + The configuration of the puzzle is stored in a 2-dimensional + list (a list of lists) 'cells'. + """ + self.cells = [] + numbers = numbers[:] # Make a copy so as not to cause side-effects. + numbers.reverse() + for row in range( 3 ): + self.cells.append( [] ) + for col in range( 3 ): + self.cells[row].append( numbers.pop() ) + if self.cells[row][col] == 0: + self.blankLocation = row, col + + def isGoal( self ): + """ + Checks to see if the puzzle is in its goal state. + + ------------- + | | 1 | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).isGoal() + True + + >>> EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).isGoal() + False + """ + current = 0 + for row in range( 3 ): + for col in range( 3 ): + if current != self.cells[row][col]: + return False + current += 1 + return True + + def legalMoves( self ): + """ + Returns a list of legal moves from the current state. + + Moves consist of moving the blank space up, down, left or right. + These are encoded as 'up', 'down', 'left' and 'right' respectively. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).legalMoves() + ['down', 'right'] + """ + moves = [] + row, col = self.blankLocation + if(row != 0): + moves.append('up') + if(row != 2): + moves.append('down') + if(col != 0): + moves.append('left') + if(col != 2): + moves.append('right') + return moves + + def result(self, move): + """ + Returns a new eightPuzzle with the current state and blankLocation + updated based on the provided move. + + The move should be a string drawn from a list returned by legalMoves. + Illegal moves will raise an exception, which may be an array bounds + exception. + + NOTE: This function *does not* change the current object. Instead, + it returns a new object. + """ + row, col = self.blankLocation + if(move == 'up'): + newrow = row - 1 + newcol = col + elif(move == 'down'): + newrow = row + 1 + newcol = col + elif(move == 'left'): + newrow = row + newcol = col - 1 + elif(move == 'right'): + newrow = row + newcol = col + 1 + else: + raise "Illegal Move" + + # Create a copy of the current eightPuzzle + newPuzzle = EightPuzzleState([0, 0, 0, 0, 0, 0, 0, 0, 0]) + newPuzzle.cells = [values[:] for values in self.cells] + # And update it to reflect the move + newPuzzle.cells[row][col] = self.cells[newrow][newcol] + newPuzzle.cells[newrow][newcol] = self.cells[row][col] + newPuzzle.blankLocation = newrow, newcol + + return newPuzzle + + # Utilities for comparison and display + def __eq__(self, other): + """ + Overloads '==' such that two eightPuzzles with the same configuration + are equal. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]) == \ + EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).result('left') + True + """ + for row in range( 3 ): + if self.cells[row] != other.cells[row]: + return False + return True + + def __hash__(self): + return hash(str(self.cells)) + + def __getAsciiString(self): + """ + Returns a display string for the maze + """ + lines = [] + horizontalLine = ('-' * (13)) + lines.append(horizontalLine) + for row in self.cells: + rowLine = '|' + for col in row: + if col == 0: + col = ' ' + rowLine = rowLine + ' ' + col.__str__() + ' |' + lines.append(rowLine) + lines.append(horizontalLine) + return '\n'.join(lines) + + def __str__(self): + return self.__getAsciiString() + +# TODO: Implement The methods in this class + +class EightPuzzleSearchProblem(search.SearchProblem): + """ + Implementation of a SearchProblem for the Eight Puzzle domain + + Each state is represented by an instance of an eightPuzzle. + """ + def __init__(self,puzzle): + "Creates a new EightPuzzleSearchProblem which stores search information." + self.puzzle = puzzle + + def getStartState(self): + return puzzle + + def isGoalState(self,state): + return state.isGoal() + + def getSuccessors(self,state): + """ + Returns list of (successor, action, stepCost) pairs where + each succesor is either left, right, up, or down + from the original state and the cost is 1.0 for each + """ + succ = [] + for a in state.legalMoves(): + succ.append((state.result(a), a, 1)) + return succ + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. The sequence must + be composed of legal moves + """ + return len(actions) + +EIGHT_PUZZLE_DATA = [[1, 0, 2, 3, 4, 5, 6, 7, 8], + [1, 7, 8, 2, 3, 4, 5, 6, 0], + [4, 3, 2, 7, 0, 5, 1, 6, 8], + [5, 1, 3, 4, 0, 2, 6, 7, 8], + [1, 2, 5, 7, 6, 8, 0, 4, 3], + [0, 3, 1, 6, 8, 2, 7, 5, 4]] + +def loadEightPuzzle(puzzleNumber): + """ + puzzleNumber: The number of the eight puzzle to load. + + Returns an eight puzzle object generated from one of the + provided puzzles in EIGHT_PUZZLE_DATA. + + puzzleNumber can range from 0 to 5. + + >>> print loadEightPuzzle(0) + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + """ + return EightPuzzleState(EIGHT_PUZZLE_DATA[puzzleNumber]) + +def createRandomEightPuzzle(moves=100): + """ + moves: number of random moves to apply + + Creates a random eight puzzle by applying + a series of 'moves' random moves to a solved + puzzle. + """ + puzzle = EightPuzzleState([0,1,2,3,4,5,6,7,8]) + for i in range(moves): + # Execute a random legal move + puzzle = puzzle.result(random.sample(puzzle.legalMoves(), 1)[0]) + return puzzle + +if __name__ == '__main__': + puzzle = createRandomEightPuzzle(25) + print('A random puzzle:') + print(puzzle) + + problem = EightPuzzleSearchProblem(puzzle) + path = search.breadthFirstSearch(problem) + print('BFS found a path of %d moves: %s' % (len(path), str(path))) + curr = puzzle + i = 1 + for a in path: + curr = curr.result(a) + print('After %d move%s: %s' % (i, ("", "s")[i>1], a)) + print(curr) + + raw_input("Press return for the next state...") # wait for key stroke + i += 1 diff --git a/search/game.py b/search/game.py new file mode 100644 index 0000000..e34d6cf --- /dev/null +++ b/search/game.py @@ -0,0 +1,729 @@ +# game.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# game.py +# ------- +# Licensing Information: Please do not distribute or publish solutions to this +# project. You are free to use and extend these projects for educational +# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by +# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html + +from util import * +import time, os +import traceback +import sys + +####################### +# Parts worth reading # +####################### + +class Agent: + """ + An agent must define a getAction method, but may also define the + following methods which will be called if they exist: + + def registerInitialState(self, state): # inspects the starting state + """ + def __init__(self, index=0): + self.index = index + + def getAction(self, state): + """ + The Agent will receive a GameState (from either {pacman, capture, sonar}.py) and + must return an action from Directions.{North, South, East, West, Stop} + """ + raiseNotDefined() + +class Directions: + NORTH = 'North' + SOUTH = 'South' + EAST = 'East' + WEST = 'West' + STOP = 'Stop' + + LEFT = {NORTH: WEST, + SOUTH: EAST, + EAST: NORTH, + WEST: SOUTH, + STOP: STOP} + + RIGHT = dict([(y,x) for x, y in LEFT.items()]) + + REVERSE = {NORTH: SOUTH, + SOUTH: NORTH, + EAST: WEST, + WEST: EAST, + STOP: STOP} + +class Configuration: + """ + A Configuration holds the (x,y) coordinate of a character, along with its + traveling direction. + + The convention for positions, like a graph, is that (0,0) is the lower left corner, x increases + horizontally and y increases vertically. Therefore, north is the direction of increasing y, or (0,1). + """ + + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def getPosition(self): + return (self.pos) + + def getDirection(self): + return self.direction + + def isInteger(self): + x,y = self.pos + return x == int(x) and y == int(y) + + def __eq__(self, other): + if other == None: return False + return (self.pos == other.pos and self.direction == other.direction) + + def __hash__(self): + x = hash(self.pos) + y = hash(self.direction) + return hash(x + 13 * y) + + def __str__(self): + return "(x,y)="+str(self.pos)+", "+str(self.direction) + + def generateSuccessor(self, vector): + """ + Generates a new configuration reached by translating the current + configuration by the action vector. This is a low-level call and does + not attempt to respect the legality of the movement. + + Actions are movement vectors. + """ + x, y= self.pos + dx, dy = vector + direction = Actions.vectorToDirection(vector) + if direction == Directions.STOP: + direction = self.direction # There is no stop direction + return Configuration((x + dx, y+dy), direction) + +class AgentState: + """ + AgentStates hold the state of an agent (configuration, speed, scared, etc). + """ + + def __init__( self, startConfiguration, isPacman ): + self.start = startConfiguration + self.configuration = startConfiguration + self.isPacman = isPacman + self.scaredTimer = 0 + self.numCarrying = 0 + self.numReturned = 0 + + def __str__( self ): + if self.isPacman: + return "Pacman: " + str( self.configuration ) + else: + return "Ghost: " + str( self.configuration ) + + def __eq__( self, other ): + if other == None: + return False + return self.configuration == other.configuration and self.scaredTimer == other.scaredTimer + + def __hash__(self): + return hash(hash(self.configuration) + 13 * hash(self.scaredTimer)) + + def copy( self ): + state = AgentState( self.start, self.isPacman ) + state.configuration = self.configuration + state.scaredTimer = self.scaredTimer + state.numCarrying = self.numCarrying + state.numReturned = self.numReturned + return state + + def getPosition(self): + if self.configuration == None: return None + return self.configuration.getPosition() + + def getDirection(self): + return self.configuration.getDirection() + +class Grid: + """ + A 2-dimensional array of objects backed by a list of lists. Data is accessed + via grid[x][y] where (x,y) are positions on a Pacman map with x horizontal, + y vertical and the origin (0,0) in the bottom left corner. + + The __str__ method constructs an output that is oriented like a pacman board. + """ + def __init__(self, width, height, initialValue=False, bitRepresentation=None): + if initialValue not in [False, True]: raise Exception('Grids can only contain booleans') + self.CELLS_PER_INT = 30 + + self.width = width + self.height = height + self.data = [[initialValue for y in range(height)] for x in range(width)] + if bitRepresentation: + self._unpackBits(bitRepresentation) + + def __getitem__(self, i): + return self.data[i] + + def __setitem__(self, key, item): + self.data[key] = item + + def __str__(self): + out = [[str(self.data[x][y])[0] for x in range(self.width)] for y in range(self.height)] + out.reverse() + return '\n'.join([''.join(x) for x in out]) + + def __eq__(self, other): + if other == None: return False + return self.data == other.data + + def __hash__(self): + # return hash(str(self)) + base = 1 + h = 0 + for l in self.data: + for i in l: + if i: + h += base + base *= 2 + return hash(h) + + def copy(self): + g = Grid(self.width, self.height) + g.data = [x[:] for x in self.data] + return g + + def deepCopy(self): + return self.copy() + + def shallowCopy(self): + g = Grid(self.width, self.height) + g.data = self.data + return g + + def count(self, item =True ): + return sum([x.count(item) for x in self.data]) + + def asList(self, key = True): + list = [] + for x in range(self.width): + for y in range(self.height): + if self[x][y] == key: list.append( (x,y) ) + return list + + def packBits(self): + """ + Returns an efficient int list representation + + (width, height, bitPackedInts...) + """ + bits = [self.width, self.height] + currentInt = 0 + for i in range(self.height * self.width): + bit = self.CELLS_PER_INT - (i % self.CELLS_PER_INT) - 1 + x, y = self._cellIndexToPosition(i) + if self[x][y]: + currentInt += 2 ** bit + if (i + 1) % self.CELLS_PER_INT == 0: + bits.append(currentInt) + currentInt = 0 + bits.append(currentInt) + return tuple(bits) + + def _cellIndexToPosition(self, index): + x = index / self.height + y = index % self.height + return x, y + + def _unpackBits(self, bits): + """ + Fills in data from a bit-level representation + """ + cell = 0 + for packed in bits: + for bit in self._unpackInt(packed, self.CELLS_PER_INT): + if cell == self.width * self.height: break + x, y = self._cellIndexToPosition(cell) + self[x][y] = bit + cell += 1 + + def _unpackInt(self, packed, size): + bools = [] + if packed < 0: raise ValueError, "must be a positive integer" + for i in range(size): + n = 2 ** (self.CELLS_PER_INT - i - 1) + if packed >= n: + bools.append(True) + packed -= n + else: + bools.append(False) + return bools + +def reconstituteGrid(bitRep): + if type(bitRep) is not type((1,2)): + return bitRep + width, height = bitRep[:2] + return Grid(width, height, bitRepresentation= bitRep[2:]) + +#################################### +# Parts you shouldn't have to read # +#################################### + +class Actions: + """ + A collection of static methods for manipulating move actions. + """ + # Directions + _directions = {Directions.NORTH: (0, 1), + Directions.SOUTH: (0, -1), + Directions.EAST: (1, 0), + Directions.WEST: (-1, 0), + Directions.STOP: (0, 0)} + + _directionsAsList = _directions.items() + + TOLERANCE = .001 + + def reverseDirection(action): + if action == Directions.NORTH: + return Directions.SOUTH + if action == Directions.SOUTH: + return Directions.NORTH + if action == Directions.EAST: + return Directions.WEST + if action == Directions.WEST: + return Directions.EAST + return action + reverseDirection = staticmethod(reverseDirection) + + def vectorToDirection(vector): + dx, dy = vector + if dy > 0: + return Directions.NORTH + if dy < 0: + return Directions.SOUTH + if dx < 0: + return Directions.WEST + if dx > 0: + return Directions.EAST + return Directions.STOP + vectorToDirection = staticmethod(vectorToDirection) + + def directionToVector(direction, speed = 1.0): + dx, dy = Actions._directions[direction] + return (dx * speed, dy * speed) + directionToVector = staticmethod(directionToVector) + + def getPossibleActions(config, walls): + possible = [] + x, y = config.pos + x_int, y_int = int(x + 0.5), int(y + 0.5) + + # In between grid points, all agents must continue straight + if (abs(x - x_int) + abs(y - y_int) > Actions.TOLERANCE): + return [config.getDirection()] + + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_y = y_int + dy + next_x = x_int + dx + if not walls[next_x][next_y]: possible.append(dir) + + return possible + + getPossibleActions = staticmethod(getPossibleActions) + + def getLegalNeighbors(position, walls): + x,y = position + x_int, y_int = int(x + 0.5), int(y + 0.5) + neighbors = [] + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_x = x_int + dx + if next_x < 0 or next_x == walls.width: continue + next_y = y_int + dy + if next_y < 0 or next_y == walls.height: continue + if not walls[next_x][next_y]: neighbors.append((next_x, next_y)) + return neighbors + getLegalNeighbors = staticmethod(getLegalNeighbors) + + def getSuccessor(position, action): + dx, dy = Actions.directionToVector(action) + x, y = position + return (x + dx, y + dy) + getSuccessor = staticmethod(getSuccessor) + +class GameStateData: + """ + + """ + def __init__( self, prevState = None ): + """ + Generates a new data packet by copying information from its predecessor. + """ + if prevState != None: + self.food = prevState.food.shallowCopy() + self.capsules = prevState.capsules[:] + self.agentStates = self.copyAgentStates( prevState.agentStates ) + self.layout = prevState.layout + self._eaten = prevState._eaten + self.score = prevState.score + + self._foodEaten = None + self._foodAdded = None + self._capsuleEaten = None + self._agentMoved = None + self._lose = False + self._win = False + self.scoreChange = 0 + + def deepCopy( self ): + state = GameStateData( self ) + state.food = self.food.deepCopy() + state.layout = self.layout.deepCopy() + state._agentMoved = self._agentMoved + state._foodEaten = self._foodEaten + state._foodAdded = self._foodAdded + state._capsuleEaten = self._capsuleEaten + return state + + def copyAgentStates( self, agentStates ): + copiedStates = [] + for agentState in agentStates: + copiedStates.append( agentState.copy() ) + return copiedStates + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + if other == None: return False + # TODO Check for type of other + if not self.agentStates == other.agentStates: return False + if not self.food == other.food: return False + if not self.capsules == other.capsules: return False + if not self.score == other.score: return False + return True + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + for i, state in enumerate( self.agentStates ): + try: + int(hash(state)) + except TypeError, e: + print e + #hash(state) + return int((hash(tuple(self.agentStates)) + 13*hash(self.food) + 113* hash(tuple(self.capsules)) + 7 * hash(self.score)) % 1048575 ) + + def __str__( self ): + width, height = self.layout.width, self.layout.height + map = Grid(width, height) + if type(self.food) == type((1,2)): + self.food = reconstituteGrid(self.food) + for x in range(width): + for y in range(height): + food, walls = self.food, self.layout.walls + map[x][y] = self._foodWallStr(food[x][y], walls[x][y]) + + for agentState in self.agentStates: + if agentState == None: continue + if agentState.configuration == None: continue + x,y = [int( i ) for i in nearestPoint( agentState.configuration.pos )] + agent_dir = agentState.configuration.direction + if agentState.isPacman: + map[x][y] = self._pacStr( agent_dir ) + else: + map[x][y] = self._ghostStr( agent_dir ) + + for x, y in self.capsules: + map[x][y] = 'o' + + return str(map) + ("\nScore: %d\n" % self.score) + + def _foodWallStr( self, hasFood, hasWall ): + if hasFood: + return '.' + elif hasWall: + return '%' + else: + return ' ' + + def _pacStr( self, dir ): + if dir == Directions.NORTH: + return 'v' + if dir == Directions.SOUTH: + return '^' + if dir == Directions.WEST: + return '>' + return '<' + + def _ghostStr( self, dir ): + return 'G' + if dir == Directions.NORTH: + return 'M' + if dir == Directions.SOUTH: + return 'W' + if dir == Directions.WEST: + return '3' + return 'E' + + def initialize( self, layout, numGhostAgents ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.food = layout.food.copy() + #self.capsules = [] + self.capsules = layout.capsules[:] + self.layout = layout + self.score = 0 + self.scoreChange = 0 + + self.agentStates = [] + numGhosts = 0 + for isPacman, pos in layout.agentPositions: + if not isPacman: + if numGhosts == numGhostAgents: continue # Max ghosts reached already + else: numGhosts += 1 + self.agentStates.append( AgentState( Configuration( pos, Directions.STOP), isPacman) ) + self._eaten = [False for a in self.agentStates] + +try: + import boinc + _BOINC_ENABLED = True +except: + _BOINC_ENABLED = False + +class Game: + """ + The Game manages the control flow, soliciting actions from agents. + """ + + def __init__( self, agents, display, rules, startingIndex=0, muteAgents=False, catchExceptions=False ): + self.agentCrashed = False + self.agents = agents + self.display = display + self.rules = rules + self.startingIndex = startingIndex + self.gameOver = False + self.muteAgents = muteAgents + self.catchExceptions = catchExceptions + self.moveHistory = [] + self.totalAgentTimes = [0 for agent in agents] + self.totalAgentTimeWarnings = [0 for agent in agents] + self.agentTimeout = False + import cStringIO + self.agentOutput = [cStringIO.StringIO() for agent in agents] + + def getProgress(self): + if self.gameOver: + return 1.0 + else: + return self.rules.getProgress(self) + + def _agentCrash( self, agentIndex, quiet=False): + "Helper method for handling agent crashes" + if not quiet: traceback.print_exc() + self.gameOver = True + self.agentCrashed = True + self.rules.agentCrash(self, agentIndex) + + OLD_STDOUT = None + OLD_STDERR = None + + def mute(self, agentIndex): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + import cStringIO + OLD_STDOUT = sys.stdout + OLD_STDERR = sys.stderr + sys.stdout = self.agentOutput[agentIndex] + sys.stderr = self.agentOutput[agentIndex] + + def unmute(self): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + # Revert stdout/stderr to originals + sys.stdout = OLD_STDOUT + sys.stderr = OLD_STDERR + + + def run( self ): + """ + Main control loop for game play. + """ + self.display.initialize(self.state.data) + self.numMoves = 0 + + ###self.display.initialize(self.state.makeObservation(1).data) + # inform learning agents of the game start + for i in range(len(self.agents)): + agent = self.agents[i] + if not agent: + self.mute(i) + # this is a null agent, meaning it failed to load + # the other team wins + print >>sys.stderr, "Agent %d failed to load" % i + self.unmute() + self._agentCrash(i, quiet=True) + return + if ("registerInitialState" in dir(agent)): + self.mute(i) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.registerInitialState, int(self.rules.getMaxStartupTime(i))) + try: + start_time = time.time() + timed_func(self.state.deepCopy()) + time_taken = time.time() - start_time + self.totalAgentTimes[i] += time_taken + except TimeoutFunctionException: + print >>sys.stderr, "Agent %d ran out of time on startup!" % i + self.unmute() + self.agentTimeout = True + self._agentCrash(i, quiet=True) + return + except Exception,data: + self._agentCrash(i, quiet=False) + self.unmute() + return + else: + agent.registerInitialState(self.state.deepCopy()) + ## TODO: could this exceed the total time + self.unmute() + + agentIndex = self.startingIndex + numAgents = len( self.agents ) + + while not self.gameOver: + # Fetch the next agent + agent = self.agents[agentIndex] + move_time = 0 + skip_action = False + # Generate an observation of the state + if 'observationFunction' in dir( agent ): + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.observationFunction, int(self.rules.getMoveTimeout(agentIndex))) + try: + start_time = time.time() + observation = timed_func(self.state.deepCopy()) + except TimeoutFunctionException: + skip_action = True + move_time += time.time() - start_time + self.unmute() + except Exception,data: + self._agentCrash(agentIndex, quiet=False) + self.unmute() + return + else: + observation = agent.observationFunction(self.state.deepCopy()) + self.unmute() + else: + observation = self.state.deepCopy() + + # Solicit an action + action = None + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.getAction, int(self.rules.getMoveTimeout(agentIndex)) - int(move_time)) + try: + start_time = time.time() + if skip_action: + raise TimeoutFunctionException() + action = timed_func( observation ) + except TimeoutFunctionException: + print >>sys.stderr, "Agent %d timed out on a single move!" % agentIndex + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + move_time += time.time() - start_time + + if move_time > self.rules.getMoveWarningTime(agentIndex): + self.totalAgentTimeWarnings[agentIndex] += 1 + print >>sys.stderr, "Agent %d took too long to make a move! This is warning %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]) + if self.totalAgentTimeWarnings[agentIndex] > self.rules.getMaxTimeWarnings(agentIndex): + print >>sys.stderr, "Agent %d exceeded the maximum number of warnings: %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + self.totalAgentTimes[agentIndex] += move_time + #print "Agent: %d, time: %f, total: %f" % (agentIndex, move_time, self.totalAgentTimes[agentIndex]) + if self.totalAgentTimes[agentIndex] > self.rules.getMaxTotalTime(agentIndex): + print >>sys.stderr, "Agent %d ran out of time! (time: %1.2f)" % (agentIndex, self.totalAgentTimes[agentIndex]) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + self.unmute() + except Exception,data: + self._agentCrash(agentIndex) + self.unmute() + return + else: + action = agent.getAction(observation) + self.unmute() + + # Execute the action + self.moveHistory.append( (agentIndex, action) ) + if self.catchExceptions: + try: + self.state = self.state.generateSuccessor( agentIndex, action ) + except Exception,data: + self.mute(agentIndex) + self._agentCrash(agentIndex) + self.unmute() + return + else: + self.state = self.state.generateSuccessor( agentIndex, action ) + + # Change the display + self.display.update( self.state.data ) + ###idx = agentIndex - agentIndex % 2 + 1 + ###self.display.update( self.state.makeObservation(idx).data ) + + # Allow for game specific conditions (winning, losing, etc.) + self.rules.process(self.state, self) + # Track progress + if agentIndex == numAgents + 1: self.numMoves += 1 + # Next agent + agentIndex = ( agentIndex + 1 ) % numAgents + + if _BOINC_ENABLED: + boinc.set_fraction_done(self.getProgress()) + + # inform a learning agent of the game result + for agentIndex, agent in enumerate(self.agents): + if "final" in dir( agent ) : + try: + self.mute(agentIndex) + agent.final( self.state ) + self.unmute() + except Exception,data: + if not self.catchExceptions: raise + self._agentCrash(agentIndex) + self.unmute() + return + self.display.finish() diff --git a/search/game.pyc b/search/game.pyc new file mode 100644 index 0000000000000000000000000000000000000000..a54706396fcd514bf90f6ce6119acd2b8bd81947 GIT binary patch literal 24953 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 0 + + speed = 1 + if isScared: speed = 0.5 + + actionVectors = [Actions.directionToVector( a, speed ) for a in legalActions] + newPositions = [( pos[0]+a[0], pos[1]+a[1] ) for a in actionVectors] + pacmanPosition = state.getPacmanPosition() + + # Select best actions given the state + distancesToPacman = [manhattanDistance( pos, pacmanPosition ) for pos in newPositions] + if isScared: + bestScore = max( distancesToPacman ) + bestProb = self.prob_scaredFlee + else: + bestScore = min( distancesToPacman ) + bestProb = self.prob_attack + bestActions = [action for action, distance in zip( legalActions, distancesToPacman ) if distance == bestScore] + + # Construct distribution + dist = util.Counter() + for a in bestActions: dist[a] = bestProb / len(bestActions) + for a in legalActions: dist[a] += ( 1-bestProb ) / len(legalActions) + dist.normalize() + return dist diff --git a/search/ghostAgents.pyc b/search/ghostAgents.pyc new file mode 100644 index 0000000000000000000000000000000000000000..5f34254d364d0d53e5fbd2464501c755d12bb1d9 GIT binary patch literal 3611 zcmcImOK%)S5U!rthu2;|!ATq>GK7SzP%s=K1tN>!!~`KB$C|aUu$HVQJMHysc4zFF zcH%_4CnNp?95^BI2S6f%Lkuz^fcUMyCMn~t5kGIwdyweuXcbF`V0vw3Rg zsZ*fM!f-85n|TTgq!n!`3XAj!(E-s%n?(x8NEd}#8dAq793N08hSU;;IjUC55Hb1} zUqxYL^5~YEIyQD%H~(Q#Eo=?lcK zh~iN1SNM#{@d4f5@|j*ivt9NsM&YH}FTZ{Bu=e$nU%#q-|HGp{zx(IGOJh(h3=eS}s)~cPrmttA+$)F`Ly09aReG%rv^DHmveA zy1B=8GJIWI$Nuo;)%ELZq5`b9%`q%Dwv!~&*V3f(^hr^)FML@~u%gj&E(H4{(OSuh zZvl3a?_@oanpShF=nQSnh!N#YVPK#<#b{Shm(@si9duuty)@3ez`L64#YU%|j$u5Q z3?zQRKtWz|8;gMju(t&zcxGFBJ85z|3U%noyBAkQ*Zu_G$XZT=DATKnSXvOgiH74NT=5dOLfC9(eoHt_}}psAg}XTlU1SV35Fg7Nu;_wr&RP zzRA43IND0mPW!HhJ*ix|C{SUsxLsv#%jkwsfs^f@;b_rv#Q9wIwv~@(sE0@+-q&?$ zY4;Ruz!6(HK^I1Se`|sjzP~Sv1uP_3)qR`|obUq{vL7oY&=`hy3{iu2bWc}UH1RG$ zj4xPb?F1u39iojGJm=6dj>4qMYJsNIxUy1ky!PkN8Gc8-9wrUIhmVY8kL_l-u?=@|KSEE#pJYr(nTHe2#+t3+P6cI7B0`xnD;U z&m~pDPh5wwAdu#@`*;TXicM6AwUVVq-eW0vIQGc%IUZwAj#Cx9htpV&dZJ_M) zr*xC;gmH{?1-34n?*#RVTyC_mqe$HX9XNEuq1}LbW3p)sn_lHjU17O^UEsWMoTm=S z6#tHI*<+tluOt!`*?W8A!lDEg^DG#`CXRK-2bcUzy$KP_QSj34gvei(*f37LNs8wQ z5$zdznduPTOp4KQ!~ypgsfI^RQE!^M_$x~SKc~7=@|JU|H$!HMTGJxbEbX7B?hKBd zly^-Md;oJOZ@xRrYFOI6p*Hr&% zR24kg5+PzZLSp_NU#^J1bFxCgAih@R1TT@wp9fj|aj>?QWRX-83}K7b^{As$pLrUN zAK{<~Tu(kkHJGGA#d+&r0|q`jveYD&eBXll5q$Fl-@}@zW;Jc2* z(OCx<(6Tqsku!&aYEDf%h`UsB&Z>*5?4CvpE~~OrMi98*mKCstcAArfM51Am@K1n+ zEiRbtNM0EoVsu&n_t((+bwxo<=PR7PqzG-I;+gx;IoX=(y|H|B)W7EgR{sYjDaoqp_ literal 0 HcmV?d00001 diff --git a/search/grading.py b/search/grading.py new file mode 100644 index 0000000..b63c877 --- /dev/null +++ b/search/grading.py @@ -0,0 +1,323 @@ +# grading.py +# ---------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +"Common code for autograders" + +import cgi +import time +import sys +import json +import traceback +import pdb +from collections import defaultdict +import util + +class Grades: + "A data structure for project grades, along with formatting code to display them" + def __init__(self, projectName, questionsAndMaxesList, + gsOutput=False, edxOutput=False, muteOutput=False): + """ + Defines the grading scheme for a project + projectName: project name + questionsAndMaxesDict: a list of (question name, max points per question) + """ + self.questions = [el[0] for el in questionsAndMaxesList] + self.maxes = dict(questionsAndMaxesList) + self.points = Counter() + self.messages = dict([(q, []) for q in self.questions]) + self.project = projectName + self.start = time.localtime()[1:6] + self.sane = True # Sanity checks + self.currentQuestion = None # Which question we're grading + self.edxOutput = edxOutput + self.gsOutput = gsOutput # GradeScope output + self.mute = muteOutput + self.prereqs = defaultdict(set) + + #print 'Autograder transcript for %s' % self.project + print 'Starting on %d-%d at %d:%02d:%02d' % self.start + + def addPrereq(self, question, prereq): + self.prereqs[question].add(prereq) + + def grade(self, gradingModule, exceptionMap = {}, bonusPic = False): + """ + Grades each question + gradingModule: the module with all the grading functions (pass in with sys.modules[__name__]) + """ + + completedQuestions = set([]) + for q in self.questions: + print '\nQuestion %s' % q + print '=' * (9 + len(q)) + print + self.currentQuestion = q + + incompleted = self.prereqs[q].difference(completedQuestions) + if len(incompleted) > 0: + prereq = incompleted.pop() + print \ +"""*** NOTE: Make sure to complete Question %s before working on Question %s, +*** because Question %s builds upon your answer for Question %s. +""" % (prereq, q, q, prereq) + continue + + if self.mute: util.mutePrint() + try: + util.TimeoutFunction(getattr(gradingModule, q),1800)(self) # Call the question's function + #TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function + except Exception, inst: + self.addExceptionMessage(q, inst, traceback) + self.addErrorHints(exceptionMap, inst, q[1]) + except: + self.fail('FAIL: Terminated with a string exception.') + finally: + if self.mute: util.unmutePrint() + + if self.points[q] >= self.maxes[q]: + completedQuestions.add(q) + + print '\n### Question %s: %d/%d ###\n' % (q, self.points[q], self.maxes[q]) + + + print '\nFinished at %d:%02d:%02d' % time.localtime()[3:6] + print "\nProvisional grades\n==================" + + for q in self.questions: + print 'Question %s: %d/%d' % (q, self.points[q], self.maxes[q]) + print '------------------' + print 'Total: %d/%d' % (self.points.totalCount(), sum(self.maxes.values())) + if bonusPic and self.points.totalCount() == 25: + print """ + + ALL HAIL GRANDPAC. + LONG LIVE THE GHOSTBUSTING KING. + + --- ---- --- + | \ / + \ / | + | + \--/ \--/ + | + | + + | + | + + + | + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@ + V @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + /\ @@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@ + /\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + +""" + print """ +Your grades are NOT yet registered. To register your grades, make sure +to follow your instructor's guidelines to receive credit on your project. +""" + + if self.edxOutput: + self.produceOutput() + if self.gsOutput: + self.produceGradeScopeOutput() + + def addExceptionMessage(self, q, inst, traceback): + """ + Method to format the exception message, this is more complicated because + we need to cgi.escape the traceback but wrap the exception in a
 tag
+    """
+    self.fail('FAIL: Exception raised: %s' % inst)
+    self.addMessage('')
+    for line in traceback.format_exc().split('\n'):
+        self.addMessage(line)
+
+  def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+    typeOf = str(type(errorInstance))
+    questionName = 'q' + questionNum
+    errorHint = ''
+
+    # question specific error hints
+    if exceptionMap.get(questionName):
+      questionMap = exceptionMap.get(questionName)
+      if (questionMap.get(typeOf)):
+        errorHint = questionMap.get(typeOf)
+    # fall back to general error messages if a question specific
+    # one does not exist
+    if (exceptionMap.get(typeOf)):
+      errorHint = exceptionMap.get(typeOf)
+
+    # dont include the HTML if we have no error hint
+    if not errorHint:
+      return ''
+
+    for line in errorHint.split('\n'):
+      self.addMessage(line)
+
+  def produceGradeScopeOutput(self):
+    out_dct = {}
+
+    # total of entire submission
+    total_possible = sum(self.maxes.values())
+    total_score = sum(self.points.values())
+    out_dct['score'] = total_score
+    out_dct['max_score'] = total_possible
+    out_dct['output'] = "Total score (%d / %d)" % (total_score, total_possible)
+
+    # individual tests
+    tests_out = []
+    for name in self.questions:
+      test_out = {}
+      # test name
+      test_out['name'] = name
+      # test score
+      test_out['score'] = self.points[name]
+      test_out['max_score'] = self.maxes[name]
+      # others
+      is_correct = self.points[name] >= self.maxes[name]
+      test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+          num=(name[1] if len(name) == 2 else name),
+          points=test_out['score'],
+          max=test_out['max_score'],
+          correct=('X' if not is_correct else ''),
+      )
+      test_out['tags'] = []
+      tests_out.append(test_out)
+    out_dct['tests'] = tests_out
+
+    # file output
+    with open('gradescope_response.json', 'w') as outfile:
+        json.dump(out_dct, outfile)
+    return
+
+  def produceOutput(self):
+    edxOutput = open('edx_response.html', 'w')
+    edxOutput.write("
") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '' + if (total_score >= total_possible): + checkOrX = '' + header = """ +

+ Total score ({total_score} / {total_possible}) +

+ """.format(total_score = total_score, + total_possible = total_possible, + checkOrX = checkOrX + ) + edxOutput.write(header) + + for q in self.questions: + if len(q) == 2: + name = q[1] + else: + name = q + checkOrX = '' + if (self.points[q] >= self.maxes[q]): + checkOrX = '' + #messages = '\n
\n'.join(self.messages[q]) + messages = "
%s
" % '\n'.join(self.messages[q]) + output = """ +
+
+
+ Question {q} ({points}/{max}) {checkOrX} +
+
+ {messages} +
+
+
+ """.format(q = name, + max = self.maxes[q], + messages = messages, + checkOrX = checkOrX, + points = self.points[q] + ) + # print "*** output for Question %s " % q[1] + # print output + edxOutput.write(output) + edxOutput.write("
") + edxOutput.close() + edxOutput = open('edx_grade', 'w') + edxOutput.write(str(self.points.totalCount())) + edxOutput.close() + + def fail(self, message, raw=False): + "Sets sanity check bit to false and outputs a message" + self.sane = False + self.assignZeroCredit() + self.addMessage(message, raw) + + def assignZeroCredit(self): + self.points[self.currentQuestion] = 0 + + def addPoints(self, amt): + self.points[self.currentQuestion] += amt + + def deductPoints(self, amt): + self.points[self.currentQuestion] -= amt + + def assignFullCredit(self, message="", raw=False): + self.points[self.currentQuestion] = self.maxes[self.currentQuestion] + if message != "": + self.addMessage(message, raw) + + def addMessage(self, message, raw=False): + if not raw: + # We assume raw messages, formatted for HTML, are printed separately + if self.mute: util.unmutePrint() + print '*** ' + message + if self.mute: util.mutePrint() + message = cgi.escape(message) + self.messages[self.currentQuestion].append(message) + + def addMessageToEmail(self, message): + print "WARNING**** addMessageToEmail is deprecated %s" % message + for line in message.split('\n'): + pass + #print '%%% ' + line + ' %%%' + #self.messages[self.currentQuestion].append(line) + + + + + +class Counter(dict): + """ + Dict with default 0 + """ + def __getitem__(self, idx): + try: + return dict.__getitem__(self, idx) + except KeyError: + return 0 + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + diff --git a/search/grading.pyc b/search/grading.pyc new file mode 100644 index 0000000000000000000000000000000000000000..af17f4b910152c1a64fcd83277320b865a990471 GIT binary patch literal 10991 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 0.01 or self.frameTime < 0: + start = time.time() + fx, fy = self.getPosition(prevPacman) + px, py = self.getPosition(pacman) + frames = 4.0 + for i in range(1,int(frames) + 1): + pos = px*i/frames + fx*(frames-i)/frames, py*i/frames + fy*(frames-i)/frames + self.movePacman(pos, self.getDirection(pacman), image) + refresh() + sleep(abs(self.frameTime) / frames) + else: + self.movePacman(self.getPosition(pacman), self.getDirection(pacman), image) + refresh() + + def getGhostColor(self, ghost, ghostIndex): + if ghost.scaredTimer > 0: + return SCARED_COLOR + else: + return GHOST_COLORS[ghostIndex] + + def drawGhost(self, ghost, agentIndex): + pos = self.getPosition(ghost) + dir = self.getDirection(ghost) + (screen_x, screen_y) = (self.to_screen(pos) ) + coords = [] + for (x, y) in GHOST_SHAPE: + coords.append((x*self.gridSize*GHOST_SIZE + screen_x, y*self.gridSize*GHOST_SIZE + screen_y)) + + colour = self.getGhostColor(ghost, agentIndex) + body = polygon(coords, colour, filled = 1) + WHITE = formatColor(1.0, 1.0, 1.0) + BLACK = formatColor(0.0, 0.0, 0.0) + + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + leftEye = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + rightEye = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + leftPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + rightPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + ghostImageParts = [] + ghostImageParts.append(body) + ghostImageParts.append(leftEye) + ghostImageParts.append(rightEye) + ghostImageParts.append(leftPupil) + ghostImageParts.append(rightPupil) + + return ghostImageParts + + def moveEyes(self, pos, dir, eyes): + (screen_x, screen_y) = (self.to_screen(pos) ) + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + moveCircle(eyes[0],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[1],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[2],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + moveCircle(eyes[3],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + + def moveGhost(self, ghost, ghostIndex, prevGhost, ghostImageParts): + old_x, old_y = self.to_screen(self.getPosition(prevGhost)) + new_x, new_y = self.to_screen(self.getPosition(ghost)) + delta = new_x - old_x, new_y - old_y + + for ghostImagePart in ghostImageParts: + move_by(ghostImagePart, delta) + refresh() + + if ghost.scaredTimer > 0: + color = SCARED_COLOR + else: + color = GHOST_COLORS[ghostIndex] + edit(ghostImageParts[0], ('fill', color), ('outline', color)) + self.moveEyes(self.getPosition(ghost), self.getDirection(ghost), ghostImageParts[-4:]) + refresh() + + def getPosition(self, agentState): + if agentState.configuration == None: return (-1000, -1000) + return agentState.getPosition() + + def getDirection(self, agentState): + if agentState.configuration == None: return Directions.STOP + return agentState.configuration.getDirection() + + def finish(self): + end_graphics() + + def to_screen(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + # Fixes some TK issue with off-center circles + def to_screen2(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + def drawWalls(self, wallMatrix): + wallColor = WALL_COLOR + for xNum, x in enumerate(wallMatrix): + if self.capture and (xNum * 2) < wallMatrix.width: wallColor = TEAM_COLORS[0] + if self.capture and (xNum * 2) >= wallMatrix.width: wallColor = TEAM_COLORS[1] + + for yNum, cell in enumerate(x): + if cell: # There's a wall here + pos = (xNum, yNum) + screen = self.to_screen(pos) + screen2 = self.to_screen2(pos) + + # draw each quadrant of the square based on adjacent walls + wIsWall = self.isWall(xNum-1, yNum, wallMatrix) + eIsWall = self.isWall(xNum+1, yNum, wallMatrix) + nIsWall = self.isWall(xNum, yNum+1, wallMatrix) + sIsWall = self.isWall(xNum, yNum-1, wallMatrix) + nwIsWall = self.isWall(xNum-1, yNum+1, wallMatrix) + swIsWall = self.isWall(xNum-1, yNum-1, wallMatrix) + neIsWall = self.isWall(xNum+1, yNum+1, wallMatrix) + seIsWall = self.isWall(xNum+1, yNum-1, wallMatrix) + + # NE quadrant + if (not nIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (0,91), 'arc') + if (nIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (eIsWall) and (not neIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (180,271), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # NW quadrant + if (not nIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (90,181), 'arc') + if (nIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (wIsWall) and (not nwIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (270,361), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # SE quadrant + if (not sIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (270,361), 'arc') + if (sIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (eIsWall) and (not seIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (90,181), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5, self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + # SW quadrant + if (not sIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (180,271), 'arc') + if (sIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (wIsWall) and (not swIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (0,91), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + def isWall(self, x, y, walls): + if x < 0 or y < 0: + return False + if x >= walls.width or y >= walls.height: + return False + return walls[x][y] + + def drawFood(self, foodMatrix ): + foodImages = [] + color = FOOD_COLOR + for xNum, x in enumerate(foodMatrix): + if self.capture and (xNum * 2) <= foodMatrix.width: color = TEAM_COLORS[0] + if self.capture and (xNum * 2) > foodMatrix.width: color = TEAM_COLORS[1] + imageRow = [] + foodImages.append(imageRow) + for yNum, cell in enumerate(x): + if cell: # There's food here + screen = self.to_screen((xNum, yNum )) + dot = circle( screen, + FOOD_SIZE * self.gridSize, + outlineColor = color, fillColor = color, + width = 1) + imageRow.append(dot) + else: + imageRow.append(None) + return foodImages + + def drawCapsules(self, capsules ): + capsuleImages = {} + for capsule in capsules: + ( screen_x, screen_y ) = self.to_screen(capsule) + dot = circle( (screen_x, screen_y), + CAPSULE_SIZE * self.gridSize, + outlineColor = CAPSULE_COLOR, + fillColor = CAPSULE_COLOR, + width = 1) + capsuleImages[capsule] = dot + return capsuleImages + + def removeFood(self, cell, foodImages ): + x, y = cell + remove_from_screen(foodImages[x][y]) + + def removeCapsule(self, cell, capsuleImages ): + x, y = cell + remove_from_screen(capsuleImages[(x, y)]) + + def drawExpandedCells(self, cells): + """ + Draws an overlay of expanded grid positions for search agents + """ + n = float(len(cells)) + baseColor = [1.0, 0.0, 0.0] + self.clearExpandedCells() + self.expandedCells = [] + for k, cell in enumerate(cells): + screenPos = self.to_screen( cell) + cellColor = formatColor(*[(n-k) * c * .5 / n + .25 for c in baseColor]) + block = square(screenPos, + 0.5 * self.gridSize, + color = cellColor, + filled = 1, behind=2) + self.expandedCells.append(block) + if self.frameTime < 0: + refresh() + + def clearExpandedCells(self): + if 'expandedCells' in dir(self) and len(self.expandedCells) > 0: + for cell in self.expandedCells: + remove_from_screen(cell) + + + def updateDistributions(self, distributions): + "Draws an agent's belief distributions" + # copy all distributions so we don't change their state + distributions = map(lambda x: x.copy(), distributions) + if self.distributionImages == None: + self.drawDistributions(self.previousState) + for x in range(len(self.distributionImages)): + for y in range(len(self.distributionImages[0])): + image = self.distributionImages[x][y] + weights = [dist[ (x,y) ] for dist in distributions] + + if sum(weights) != 0: + pass + # Fog of war + color = [0.0,0.0,0.0] + colors = GHOST_VEC_COLORS[1:] # With Pacman + if self.capture: colors = GHOST_VEC_COLORS + for weight, gcolor in zip(weights, colors): + color = [min(1.0, c + 0.95 * g * weight ** .3) for c,g in zip(color, gcolor)] + changeColor(image, formatColor(*color)) + refresh() + +class FirstPersonPacmanGraphics(PacmanGraphics): + def __init__(self, zoom = 1.0, showGhosts = True, capture = False, frameTime=0): + PacmanGraphics.__init__(self, zoom, frameTime=frameTime) + self.showGhosts = showGhosts + self.capture = capture + + def initialize(self, state, isBlue = False): + + self.isBlue = isBlue + PacmanGraphics.startGraphics(self, state) + # Initialize distribution images + walls = state.layout.walls + dist = [] + self.layout = state.layout + + # Draw the rest + self.distributionImages = None # initialize lazily + self.drawStaticObjects(state) + self.drawAgentObjects(state) + + # Information + self.previousState = state + + def lookAhead(self, config, state): + if config.getDirection() == 'Stop': + return + else: + pass + # Draw relevant ghosts + allGhosts = state.getGhostStates() + visibleGhosts = state.getVisibleGhosts() + for i, ghost in enumerate(allGhosts): + if ghost in visibleGhosts: + self.drawGhost(ghost, i) + else: + self.currentGhostImages[i] = None + + def getGhostColor(self, ghost, ghostIndex): + return GHOST_COLORS[ghostIndex] + + def getPosition(self, ghostState): + if not self.showGhosts and not ghostState.isPacman and ghostState.getPosition()[1] > 1: + return (-1000, -1000) + else: + return PacmanGraphics.getPosition(self, ghostState) + +def add(x, y): + return (x[0] + y[0], x[1] + y[1]) + + +# Saving graphical output +# ----------------------- +# Note: to make an animated gif from this postscript output, try the command: +# convert -delay 7 -loop 1 -compress lzw -layers optimize frame* out.gif +# convert is part of imagemagick (freeware) + +SAVE_POSTSCRIPT = False +POSTSCRIPT_OUTPUT_DIR = 'frames' +FRAME_NUMBER = 0 +import os + +def saveFrame(): + "Saves the current graphical output as a postscript file" + global SAVE_POSTSCRIPT, FRAME_NUMBER, POSTSCRIPT_OUTPUT_DIR + if not SAVE_POSTSCRIPT: return + if not os.path.exists(POSTSCRIPT_OUTPUT_DIR): os.mkdir(POSTSCRIPT_OUTPUT_DIR) + name = os.path.join(POSTSCRIPT_OUTPUT_DIR, 'frame_%08d.ps' % FRAME_NUMBER) + FRAME_NUMBER += 1 + writePostscript(name) # writes the current canvas diff --git a/search/graphicsDisplay.pyc b/search/graphicsDisplay.pyc new file mode 100644 index 0000000000000000000000000000000000000000..01fb52d73399abf9e5fa75d49bc4e325941282c8 GIT binary patch literal 26138 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 literal 0 HcmV?d00001 diff --git a/search/graphicsUtils.py b/search/graphicsUtils.py new file mode 100644 index 0000000..b80d3d2 --- /dev/null +++ b/search/graphicsUtils.py @@ -0,0 +1,402 @@ +# graphicsUtils.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import math +import random +import string +import time +import types +import Tkinter +import os.path + +_Windows = sys.platform == 'win32' # True if on Win95/98/NT + +_root_window = None # The root window for graphics output +_canvas = None # The canvas which holds graphics +_canvas_xs = None # Size of canvas object +_canvas_ys = None +_canvas_x = None # Current position on canvas +_canvas_y = None +_canvas_col = None # Current colour (set to black below) +_canvas_tsize = 12 +_canvas_tserifs = 0 + +def formatColor(r, g, b): + return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)) + +def colorToVector(color): + return map(lambda x: int(x, 16) / 256.0, [color[1:3], color[3:5], color[5:7]]) + +if _Windows: + _canvas_tfonts = ['times new roman', 'lucida console'] +else: + _canvas_tfonts = ['times', 'lucidasans-24'] + pass # XXX need defaults here + +def sleep(secs): + global _root_window + if _root_window == None: + time.sleep(secs) + else: + _root_window.update_idletasks() + _root_window.after(int(1000 * secs), _root_window.quit) + _root_window.mainloop() + +def begin_graphics(width=640, height=480, color=formatColor(0, 0, 0), title=None): + + global _root_window, _canvas, _canvas_x, _canvas_y, _canvas_xs, _canvas_ys, _bg_color + + # Check for duplicate call + if _root_window is not None: + # Lose the window. + _root_window.destroy() + + # Save the canvas size parameters + _canvas_xs, _canvas_ys = width - 1, height - 1 + _canvas_x, _canvas_y = 0, _canvas_ys + _bg_color = color + + # Create the root window + _root_window = Tkinter.Tk() + _root_window.protocol('WM_DELETE_WINDOW', _destroy_window) + _root_window.title(title or 'Graphics Window') + _root_window.resizable(0, 0) + + # Create the canvas object + try: + _canvas = Tkinter.Canvas(_root_window, width=width, height=height) + _canvas.pack() + draw_background() + _canvas.update() + except: + _root_window = None + raise + + # Bind to key-down and key-up events + _root_window.bind( "", _keypress ) + _root_window.bind( "", _keyrelease ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _leftclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _ctrl_leftclick) + _clear_keys() + +_leftclick_loc = None +_rightclick_loc = None +_ctrl_leftclick_loc = None + +def _leftclick(event): + global _leftclick_loc + _leftclick_loc = (event.x, event.y) + +def _rightclick(event): + global _rightclick_loc + _rightclick_loc = (event.x, event.y) + +def _ctrl_leftclick(event): + global _ctrl_leftclick_loc + _ctrl_leftclick_loc = (event.x, event.y) + +def wait_for_click(): + while True: + global _leftclick_loc + global _rightclick_loc + global _ctrl_leftclick_loc + if _leftclick_loc != None: + val = _leftclick_loc + _leftclick_loc = None + return val, 'left' + if _rightclick_loc != None: + val = _rightclick_loc + _rightclick_loc = None + return val, 'right' + if _ctrl_leftclick_loc != None: + val = _ctrl_leftclick_loc + _ctrl_leftclick_loc = None + return val, 'ctrl_left' + sleep(0.05) + +def draw_background(): + corners = [(0,0), (0, _canvas_ys), (_canvas_xs, _canvas_ys), (_canvas_xs, 0)] + polygon(corners, _bg_color, fillColor=_bg_color, filled=True, smoothed=False) + +def _destroy_window(event=None): + sys.exit(0) +# global _root_window +# _root_window.destroy() +# _root_window = None + #print "DESTROY" + +def end_graphics(): + global _root_window, _canvas, _mouse_enabled + try: + try: + sleep(1) + if _root_window != None: + _root_window.destroy() + except SystemExit, e: + print 'Ending graphics raised an exception:', e + finally: + _root_window = None + _canvas = None + _mouse_enabled = 0 + _clear_keys() + +def clear_screen(background=None): + global _canvas_x, _canvas_y + _canvas.delete('all') + draw_background() + _canvas_x, _canvas_y = 0, _canvas_ys + +def polygon(coords, outlineColor, fillColor=None, filled=1, smoothed=1, behind=0, width=1): + c = [] + for coord in coords: + c.append(coord[0]) + c.append(coord[1]) + if fillColor == None: fillColor = outlineColor + if filled == 0: fillColor = "" + poly = _canvas.create_polygon(c, outline=outlineColor, fill=fillColor, smooth=smoothed, width=width) + if behind > 0: + _canvas.tag_lower(poly, behind) # Higher should be more visible + return poly + +def square(pos, r, color, filled=1, behind=0): + x, y = pos + coords = [(x - r, y - r), (x + r, y - r), (x + r, y + r), (x - r, y + r)] + return polygon(coords, color, color, filled, 0, behind=behind) + +def circle(pos, r, outlineColor, fillColor, endpoints=None, style='pieslice', width=2): + x, y = pos + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + return _canvas.create_arc(x0, y0, x1, y1, outline=outlineColor, fill=fillColor, + extent=e[1] - e[0], start=e[0], style=style, width=width) + +def image(pos, file="../../blueghost.gif"): + x, y = pos + # img = PhotoImage(file=file) + return _canvas.create_image(x, y, image = Tkinter.PhotoImage(file=file), anchor = Tkinter.NW) + + +def refresh(): + _canvas.update_idletasks() + +def moveCircle(id, pos, r, endpoints=None): + global _canvas_x, _canvas_y + + x, y = pos +# x0, x1 = x - r, x + r + 1 +# y0, y1 = y - r, y + r + 1 + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + if os.path.isfile('flag'): + edit(id, ('extent', e[1] - e[0])) + else: + edit(id, ('start', e[0]), ('extent', e[1] - e[0])) + move_to(id, x0, y0) + +def edit(id, *args): + _canvas.itemconfigure(id, **dict(args)) + +def text(pos, color, contents, font='Helvetica', size=12, style='normal', anchor="nw"): + global _canvas_x, _canvas_y + x, y = pos + font = (font, str(size), style) + return _canvas.create_text(x, y, fill=color, text=contents, font=font, anchor=anchor) + +def changeText(id, newText, font=None, size=12, style='normal'): + _canvas.itemconfigure(id, text=newText) + if font != None: + _canvas.itemconfigure(id, font=(font, '-%d' % size, style)) + +def changeColor(id, newColor): + _canvas.itemconfigure(id, fill=newColor) + +def line(here, there, color=formatColor(0, 0, 0), width=2): + x0, y0 = here[0], here[1] + x1, y1 = there[0], there[1] + return _canvas.create_line(x0, y0, x1, y1, fill=color, width=width) + +############################################################################## +### Keypress handling ######################################################## +############################################################################## + +# We bind to key-down and key-up events. + +_keysdown = {} +_keyswaiting = {} +# This holds an unprocessed key release. We delay key releases by up to +# one call to keys_pressed() to get round a problem with auto repeat. +_got_release = None + +def _keypress(event): + global _got_release + #remap_arrows(event) + _keysdown[event.keysym] = 1 + _keyswaiting[event.keysym] = 1 +# print event.char, event.keycode + _got_release = None + +def _keyrelease(event): + global _got_release + #remap_arrows(event) + try: + del _keysdown[event.keysym] + except: + pass + _got_release = 1 + +def remap_arrows(event): + # TURN ARROW PRESSES INTO LETTERS (SHOULD BE IN KEYBOARD AGENT) + if event.char in ['a', 's', 'd', 'w']: + return + if event.keycode in [37, 101]: # LEFT ARROW (win / x) + event.char = 'a' + if event.keycode in [38, 99]: # UP ARROW + event.char = 'w' + if event.keycode in [39, 102]: # RIGHT ARROW + event.char = 'd' + if event.keycode in [40, 104]: # DOWN ARROW + event.char = 's' + +def _clear_keys(event=None): + global _keysdown, _got_release, _keyswaiting + _keysdown = {} + _keyswaiting = {} + _got_release = None + +def keys_pressed(d_o_e=Tkinter.tkinter.dooneevent, + d_w=Tkinter.tkinter.DONT_WAIT): + d_o_e(d_w) + if _got_release: + d_o_e(d_w) + return _keysdown.keys() + +def keys_waiting(): + global _keyswaiting + keys = _keyswaiting.keys() + _keyswaiting = {} + return keys + +# Block for a list of keys... + +def wait_for_keys(): + keys = [] + while keys == []: + keys = keys_pressed() + sleep(0.05) + return keys + +def remove_from_screen(x, + d_o_e=Tkinter.tkinter.dooneevent, + d_w=Tkinter.tkinter.DONT_WAIT): + _canvas.delete(x) + d_o_e(d_w) + +def _adjust_coords(coord_list, x, y): + for i in range(0, len(coord_list), 2): + coord_list[i] = coord_list[i] + x + coord_list[i + 1] = coord_list[i + 1] + y + return coord_list + +def move_to(object, x, y=None, + d_o_e=Tkinter.tkinter.dooneevent, + d_w=Tkinter.tkinter.DONT_WAIT): + if y is None: + try: x, y = x + except: raise 'incomprehensible coordinates' + + horiz = True + newCoords = [] + current_x, current_y = _canvas.coords(object)[0:2] # first point + for coord in _canvas.coords(object): + if horiz: + inc = x - current_x + else: + inc = y - current_y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + +def move_by(object, x, y=None, + d_o_e=Tkinter.tkinter.dooneevent, + d_w=Tkinter.tkinter.DONT_WAIT, lift=False): + if y is None: + try: x, y = x + except: raise Exception, 'incomprehensible coordinates' + + horiz = True + newCoords = [] + for coord in _canvas.coords(object): + if horiz: + inc = x + else: + inc = y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + if lift: + _canvas.tag_raise(object) + +def writePostscript(filename): + "Writes the current canvas to a postscript file." + psfile = file(filename, 'w') + psfile.write(_canvas.postscript(pageanchor='sw', + y='0.c', + x='0.c')) + psfile.close() + +ghost_shape = [ + (0, - 0.5), + (0.25, - 0.75), + (0.5, - 0.5), + (0.75, - 0.75), + (0.75, 0.5), + (0.5, 0.75), + (- 0.5, 0.75), + (- 0.75, 0.5), + (- 0.75, - 0.75), + (- 0.5, - 0.5), + (- 0.25, - 0.75) + ] + +if __name__ == '__main__': + begin_graphics() + clear_screen() + ghost_shape = [(x * 10 + 20, y * 10 + 20) for x, y in ghost_shape] + g = polygon(ghost_shape, formatColor(1, 1, 1)) + move_to(g, (50, 50)) + circle((150, 150), 20, formatColor(0.7, 0.3, 0.0), endpoints=[15, - 15]) + sleep(2) diff --git a/search/graphicsUtils.pyc b/search/graphicsUtils.pyc new file mode 100644 index 0000000000000000000000000000000000000000..ad94d45795c94b722eea12f130c23814bbbf317e GIT binary patch literal 14217 zcmcgyU2GiJbw0C0F1ZvbQj{o)v20VeWYMxjNtE3*HZ9ruu_XVjouN`X1ntFeX1E+` zIkTD>iliZ-iK#p!joSuD+9rMIe~T3DLyNko(E<)oBW;nOa9f~Hc@Pkwa9;`pdFxC1 zedo^3E@c&cs5X@5?7esH+}I1J<0zD@%sXv^aESO$A3#2A`3;? zk_D^PUXq1Ut8B}H-75FULQkvQD+|4?a#5eE~$F-+vd_o62A^uVAKm93cVA3gF z#z_f9+*1lqa|Cxr{3rFRGvXi9!qehErG;m7isQPNUyz_ItDlwCUu+dVC;kZ?^I7qy zwf}SCpVY!^t50X=Q`-4?@lR`?vmE?BW6mfp=kEVDctQL#ivEk@Kds}O7yl*kFN=Rg z{Hx->EdDFvzbgJ~;#b9gUHmtszDpJ^NN`d7=P0Gg`*tuP+agwQSbypw!3DYa)|4!Q zQ0)JgZg! zK0AVe*cHpi!q2m-e?fe#=n}Wj=Agpi&+u2xdQsd6qcbxh zbsH=c@u1tnr;O(Up7bODKFmaJp-_^27>pIDLc0QGH*s~3HZOM1fa*7m> zgq$%{)=)G;5~IzoHMeOheCLsXU0NIEM#sMv|5D&(adH$5Iiu2Hl(TRxNGGFUV={@? z+$i0Rw#K>_`tGC`M`_#$RJ665N}+j~qU(v7rwb1C;4|g*=Q$?WBdW&-B`e9QO{PQC zt>`r0t@fx+v(zgYe3ixbxh|~ssZR1`zjnh&b5oS8YWNArIaT2V)UjF;$5{3Fw4lyH8+eJah$V= zL#Kh4c85a_nQ)3z3;;-J&>ATXSPxs_mwh0BwpT2THCjE>x=|K(XZZid&XjC2{)%m?A2`N3IUGion1Ys5LDjsXr*UY-u*A zpJW}I5HG08vCG!8I>K&UNUzR{k)f1U$sLw#SXM`LMcE$ngN9Kp%||0*S(dL8zbg`1 z@+JpIpn*m0E9OVNU$U{zdPTDRo%Mlx>boR^jnCJQ?*}D2&{-e4r;f$L%dkI&1@*9| zxhe9aYP1(6QR$g~t# zy%8*DUL(XL^li0-{4;e$z{`>b++yBmiVErMCm1In7S3Y;-LVoG&GJ%E52ISk6MqgZ z>C*txJ6IaB$E+c%Y{Pn3d#w@MwjQzutUhbEWm{#d&mKX0=fA>klV566hzd-{s)3du zkaD5y)ChXAszw(M*`r~_maeExk6I)4+8!;q86X}(-v=LfI43abG9Kf^O8*~BoYNS` zh-aJI1t!j!*7_Bb2N^v%jEF$$2ZzL73|8AIqCXczPGVj-odOXQHTEmy>dR=@e1~2l zxkb@|L|wNfd5Y!R8Yj5w{^6`3a&rh~^B%7;u+4jD00G~lJ>bpE7%_cX-lIo7kZpQk zD{P$B4O`|_glTT7Y8|M->im`CY9|V^)xYy?{(bkHhM(H~TCyHEGx%D~r$c>no*|%3 zXvcFm-9~rBUDa&E4YL}8?V6gS1T&`x0mQ2G3_`ujQ1yF%J$(;A!=ws+83jbxpfh*@ zLW2H9!4SW$)PxC#9Uvdj2M|F4DBdF=+IGn{K9Xjj8UlzCa5-!=E-;8yQ_Nc6s|!fi z5TdOFzQ%9bUxAX>QO`8NG3b<?J2#u4^)J!!+B6f{5XR?;J;ykr#Tcpi4^0 zcq)ke!WaKK>M1>_KtpU*$351twWmboEZ;|tbop=rg&aZaNIj!-B}D9`D=BGX(5gz5 z+h}xXkB&e+^byl!fkWXnD)kUcv=4?3jPlySQWptg;;Gb2f*|@GOqz0aQW`=SqT#eh z4d!b2T)>n5IY3?HcAspXqS0Y~=XSr`>eDc!pSEg(lG<)4nT8ufGLKyh2*hMjmHUp{ zCHf(3S@01XJ&L7i%(tL@v|`*AVYh;Bv0H&%>yuk$XhYBKiriA;c)drG@9M4scerx1ThCY6*?`(gG3}Ppz&qiw@571ZP`amx2|9!8$c`86pN`W%I?+G}~F2dT+Ng@JAjGJyQv?OwSJ?to3$z=A#e zfx|KlR*@EIfEzX}M{49wr|wOY9&%q`9r0F0?73d(C$yk?v_ zG^{4bS30w+2E!UcI?PRVhnhx?ZmhdW@E4fG=oBdUvSs#IVUS|W5`gL~OGW7NkQ~i)D z5)rC5B%89$!Ba~~G+``;A9Vk>1?7_m!82N|? zkO#<%I9;HY;u|P!PkC;NJGG9@dRT|7hbsWiYk1>S0hEHlW`-S!>Oh*gNvpKg2#j|2 zu(xiIS!#r7?qR5Uxo#p2Iz{3-UnEd@xW*EdPZbtg-HfpdPMx>eu6sk8K(ou>2b-sW zI(|;;&z7leopxSAV;9B83lrGpzs4l#J^+FEOyJ+3HH>W2xa=<-vQq9Jrl(Kh|59T; zsISCnHeC;wJ+7A|q3u?Y`YZ~$NI?(~9I6*9G|TXo`h_3^{tO+E0EC#7N_kYEObFN9 zy2;hSSejkG8+j{8gVHV-?@lM1(_XtfkDx*0fHzicSVjh9<)1KB zD>x)OsW$~n%UCqnMav6&2~~vWE9F)~i!cUd8d6%|)(b7-MZeH>LT!yN?z9pEEh~Y@ z5>{eBtpw7HaNlIk?nhU9I!c@{8C zRleC4_{c8vlePF}P|ISqUHVbzci0pcJ=*f?EU`-IaHg$qf~lf7OWyfQ{!6Dg+>zciQERX2YLpVR_d8k16Vbli->1% zT}Jqj+I6@AFz0+WN=V}xQ^}>|rjq^jjKs(k7pjp0C`=caI@rz`k|O0A7+1wn+cMWS zNe|K*IiQgI7*D<}7(_^@K_Cr2?X{AUqFsm&EQE@WWsy!M=X1W}bqD?SgW90;HVzgd z-JIsmMrT!&QxfdN5#Qu7DSXaN0!pB9Ix1^aP#lI`0~Pf%=gaIwhu$5ss6X>o+^8OK z&i}+jd5pIg4qDxBOTUyrOg7(#K%h>3NT!lwl&3(lLzigafS>Lgx`dGmqhh49{1;Io z4q|x}Xs&=#sp!0l0tAD;fIqP2rQ8SU&UnJ82^!V^|bKsL|<=Jz1P zpfq)C(3Bb`#N!e^r7hgEnLwXQTM1x*I0AB3NnpZ#4da&hEohvZ;sB*C5s%B@qJDzk zToA^nDKwmXD8)*-!zh4kAPESEJi;t1(1^SOi>Z-&>*$o8S^fO(QjLJObHT zGiRzSGu?=EjfsaDh1KdfXwA<~%m%58sqtf&u!Q9r?Cl|IsGZ|4uzw7lw)E`LApHD% z)W=grrl&xN*2x8VZrr(1`>&zy{0hN)09{ArDNHgzROnU(hcY~l>1i{QqNdE;MuCWc zr9*PPEC~)Tq0t66+>&C`G}(<7#2PSh`Kw`lg-bQifO}qz=qq1U=oal8ePJls)-Btke4*g%3?_}KVD5m2O*XS6EHP_?dpEv^NcS@CNdU9zJzTb#9~ z2f%^<1o7-gqu_Q0Q&JwwWD+06l#DVS2FM^vGE4*REyPF@M|t!ul#JYI2{q#n9%R6z za;GTd_V`4bRXpt}5GYv1TO@T|`sU;05z5Sj;;0R`Z?qk}f)PPLZr!LLgG$u+b+vu) zr8xc;+S&(PP8V^cuotIWMD`gXspb+W4qFwN%;FkdfhaTFhEqU+*g!fF2C$@Nj4Z7x zW@e*(6?NkPDCy2`5d0?F@N|K3w4yK=+~zIgQdP~Q#Ak^abzR`o6em^+7r+$a=Bq@D zj$AE0@Igd`z3IibX1Ij_vIuh+6OEhVtocC@G(G|R^Hx@8Z%pL;-k&(JRT9I0qun1ITAYNJJv!>emWl(m^Iqa0eI87&(yv8COlK z7a311w=7?$yn>nLgxaSLDv$I?H;lY^4JK|$kFrTyQAorGsI(V}S0kyPXbO+IpK@e+*N#xjv#?C(kP= z8J^R5d*~|E1DXf51m{tJLxou4-o|5 z)WMwYrM%HHpCBofPGTE4nT!9VrSzBu9HRTn-Tbqfe`~1EGhr)ewY_NK9?6hX|>!fT3UsVKU7p|HD0r=)k2OK z?jx8Xpm(k>>)(HztDn)oe`|dUadg>1-|*b@#*0=1*GF zd~*NhyXQK47lVAVv-!go33fDh5TtXEqIu_(1wnT%@Z(}^vp_>Jl1;}{+9{a2f;LCh zf@+Oh`sP+D{JkHqsq`Ru$W1;yuS#0B)eKT|#?ptj7`OcKl@s$l6|LEDGhX;N%rkdh zQ{#jfY#Yln!IEC=DQ+R;mo3dTQxDfc8S}L+mS#*snj3gV!D?9=B;j)EP&FKihPma@ zdb^A?W@;{oFx)j)vz}(@8G;i8(*#sw=Q)C-1cwMN6TCve172r=V3~j%bJhsf2@(Pr z6LDY~#Q6%rR|&pGpgVx~SUN_)O}_I@g5v;c(z5(AESK-cxRtK=A=MaPe7$H zl!Mu|*k5l1XQ_hep8*VF^V{*SG^}RZ+@~ literal 0 HcmV?d00001 diff --git a/search/keyboardAgents.py b/search/keyboardAgents.py new file mode 100644 index 0000000..c7d9fcf --- /dev/null +++ b/search/keyboardAgents.py @@ -0,0 +1,84 @@ +# keyboardAgents.py +# ----------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Directions +import random + +class KeyboardAgent(Agent): + """ + An agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'a' + EAST_KEY = 'd' + NORTH_KEY = 'w' + SOUTH_KEY = 's' + STOP_KEY = 'q' + + def __init__( self, index = 0 ): + + self.lastMove = Directions.STOP + self.index = index + self.keys = [] + + def getAction( self, state): + from graphicsUtils import keys_waiting + from graphicsUtils import keys_pressed + keys = keys_waiting() + keys_pressed() + if keys != []: + self.keys = keys + + legal = state.getLegalActions(self.index) + move = self.getMove(legal) + + if move == Directions.STOP: + # Try to move in the same direction as before + if self.lastMove in legal: + move = self.lastMove + + if (self.STOP_KEY in self.keys) and Directions.STOP in legal: move = Directions.STOP + + if move not in legal: + move = random.choice(legal) + + self.lastMove = move + return move + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys or 'Left' in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys or 'Right' in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys or 'Up' in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys or 'Down' in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move + +class KeyboardAgent2(KeyboardAgent): + """ + A second agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'j' + EAST_KEY = "l" + NORTH_KEY = 'i' + SOUTH_KEY = 'k' + STOP_KEY = 'u' + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move diff --git a/search/keyboardAgents.pyc b/search/keyboardAgents.pyc new file mode 100644 index 0000000000000000000000000000000000000000..0127a83f739faff9e618a5e064c383908083802f GIT binary patch literal 2973 zcmcgt-)kII6h1SvncYpdR-zbv(XBs6A3_oJp^BK=K(uuulZ4V`Vc5=0HnTgKP3PVk z1DgkF{{Wx*2Z-Q5prEKwv8YfG^uZ?)i|@YlNkQ~{=g#bGggz9Evze3g-FwcQbMN`i zz0RHE^QS+(aUrDQarpmh{Hh*{i2p1~iFPdNT4Vv+f^F*B20N5G)O9GTQEbtcMbR8x zBf3g-v0J0arMM=e_37vwMGmcd4Gxg}&5viXrsr5J58_N4IM48GlE*{e9nrLXzgeb6SVibo4BqO{4;w{6cJ05n*mp!Yow!X3Uw#HtjeWl+T zzZ1(&CRr4}t1-{nDG$K}sW{#8u`-WeBh)Hu(dpjzKYntv_wk({KI(n(&8^=)|MSML zz3*;)_Tf+8-u$^2j-$A@{6>(*{X9#uK~J#~ZuiQiOGVVu?!IIa1WA_YAb6BNtXu@L zTUOJOW!D*jW3ueuS>Y-boU-TuoMOzJV$Rug;E*-3!KH}JkVcy zV37BBx06t9>LgYEeLU=+fH(#NXoFavj|cs9*>sK+o7XK9pocKW2Vwjse4v3t!MT-- z8pX={Sv1}eHQXL2VeG-%M@m91rTaSmcVuufMOb0k4#znvf+1;MqgmdQ3{;y1S@+AY zWwq=D>x9iemM5vj%v-Pv?typUzHsj^R!qw=>p&GFrQGT{&=P~D`YU3|?6QOmC1gj` zm(2{>jPDlsT=~8yzRVEgTf)k>UKy(o!2QbiOYv=#z9p=DnN($x>K7buQ<(oDzRY10 zw}geS%~V9qI#RFm@s^g_`pIBhYn&FFyE1cjyq9_4Rz(|O%!QTp4KT*=4y4A2Osy=N zDe(u;C|PIC-&hrnNmw8mUtimt85iWCPQo{T5!%Bnj)+bE0)P8ue$|64h!3e#)H?La z;)_!)>+i%tph2M^*cnGxQb2S^5JclJ2z;D)ej5TOx(|B#4?v)Q{YM}Wv09C2|LFY}~-DiQP~yurRk~ zTg7}~M)z`e4&|myMJ6IgBCp8(0dC-wk{yQE1>=1Zt%b(Ii&{N*gk{el*3sdZRErI ZD4SIdmRHPC@+xj`)nI|EtYtrR>K{o~j$Hr% literal 0 HcmV?d00001 diff --git a/search/layout.py b/search/layout.py new file mode 100644 index 0000000..c6b377d --- /dev/null +++ b/search/layout.py @@ -0,0 +1,149 @@ +# layout.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from util import manhattanDistance +from game import Grid +import os +import random + +VISIBILITY_MATRIX_CACHE = {} + +class Layout: + """ + A Layout manages the static information about the game board. + """ + + def __init__(self, layoutText): + self.width = len(layoutText[0]) + self.height= len(layoutText) + self.walls = Grid(self.width, self.height, False) + self.food = Grid(self.width, self.height, False) + self.capsules = [] + self.agentPositions = [] + self.numGhosts = 0 + self.processLayoutText(layoutText) + self.layoutText = layoutText + self.totalFood = len(self.food.asList()) + # self.initializeVisibilityMatrix() + + def getNumGhosts(self): + return self.numGhosts + + def initializeVisibilityMatrix(self): + global VISIBILITY_MATRIX_CACHE + if reduce(str.__add__, self.layoutText) not in VISIBILITY_MATRIX_CACHE: + from game import Directions + vecs = [(-0.5,0), (0.5,0),(0,-0.5),(0,0.5)] + dirs = [Directions.NORTH, Directions.SOUTH, Directions.WEST, Directions.EAST] + vis = Grid(self.width, self.height, {Directions.NORTH:set(), Directions.SOUTH:set(), Directions.EAST:set(), Directions.WEST:set(), Directions.STOP:set()}) + for x in range(self.width): + for y in range(self.height): + if self.walls[x][y] == False: + for vec, direction in zip(vecs, dirs): + dx, dy = vec + nextx, nexty = x + dx, y + dy + while (nextx + nexty) != int(nextx) + int(nexty) or not self.walls[int(nextx)][int(nexty)] : + vis[x][y][direction].add((nextx, nexty)) + nextx, nexty = x + dx, y + dy + self.visibility = vis + VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] = vis + else: + self.visibility = VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] + + def isWall(self, pos): + x, col = pos + return self.walls[x][col] + + def getRandomLegalPosition(self): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + while self.isWall( (x, y) ): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + return (x,y) + + def getRandomCorner(self): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + return random.choice(poses) + + def getFurthestCorner(self, pacPos): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + dist, pos = max([(manhattanDistance(p, pacPos), p) for p in poses]) + return pos + + def isVisibleFrom(self, ghostPos, pacPos, pacDirection): + row, col = [int(x) for x in pacPos] + return ghostPos in self.visibility[row][col][pacDirection] + + def __str__(self): + return "\n".join(self.layoutText) + + def deepCopy(self): + return Layout(self.layoutText[:]) + + def processLayoutText(self, layoutText): + """ + Coordinates are flipped from the input format to the (x,y) convention here + + The shape of the maze. Each character + represents a different type of object. + % - Wall + . - Food + o - Capsule + G - Ghost + P - Pacman + Other characters are ignored. + """ + maxY = self.height - 1 + for y in range(self.height): + for x in range(self.width): + layoutChar = layoutText[maxY - y][x] + self.processLayoutChar(x, y, layoutChar) + self.agentPositions.sort() + self.agentPositions = [ ( i == 0, pos) for i, pos in self.agentPositions] + + def processLayoutChar(self, x, y, layoutChar): + if layoutChar == '%': + self.walls[x][y] = True + elif layoutChar == '.': + self.food[x][y] = True + elif layoutChar == 'o': + self.capsules.append((x, y)) + elif layoutChar == 'P': + self.agentPositions.append( (0, (x, y) ) ) + elif layoutChar in ['G']: + self.agentPositions.append( (1, (x, y) ) ) + self.numGhosts += 1 + elif layoutChar in ['1', '2', '3', '4']: + self.agentPositions.append( (int(layoutChar), (x,y))) + self.numGhosts += 1 +def getLayout(name, back = 2): + if name.endswith('.lay'): + layout = tryToLoad('layouts/' + name) + if layout == None: layout = tryToLoad(name) + else: + layout = tryToLoad('layouts/' + name + '.lay') + if layout == None: layout = tryToLoad(name + '.lay') + if layout == None and back >= 0: + curdir = os.path.abspath('.') + os.chdir('..') + layout = getLayout(name, back -1) + os.chdir(curdir) + return layout + +def tryToLoad(fullname): + if(not os.path.exists(fullname)): return None + f = open(fullname) + try: return Layout([line.strip() for line in f]) + finally: f.close() diff --git a/search/layout.pyc b/search/layout.pyc new file mode 100644 index 0000000000000000000000000000000000000000..aa8efbf727fb523f8ce04f7789099662d9d57479 GIT binary patch literal 6701 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 literal 0 HcmV?d00001 diff --git a/search/layouts/bigCorners.lay b/search/layouts/bigCorners.lay new file mode 100644 index 0000000..4d89d7b --- /dev/null +++ b/search/layouts/bigCorners.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % %.% +% %%%%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % +% % % % % % %%% %%% %%% % % % % % % +% % % % % %% % % % % % % % % % +% % %%%%% % %%% %%% % %%% %%% %%%%% +% % % % % % % % % % % +% %%% % % % %%% %%% %%%%%%%%% % %%% +% % % % % % % +% %%% %%%%%%%%%%%%%%%%%%%%% % % %%% % +% % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % %P % % % % % % +% %%% %%% %%% % %%% % % %%%%% % %%%%% +% % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % +% % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/bigMaze.lay b/search/layouts/bigMaze.lay new file mode 100644 index 0000000..e11fade --- /dev/null +++ b/search/layouts/bigMaze.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % % % % % % % +% %%%%%%% % %%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % % % % +% % % % % %%% %%% %%% %%% % % % % % % +% % % % % % % % % +%%% %%%%%%% % % %%%%% %%% % %%% %%%%% +% % % % % % % % % % +%%%%% % % %%%%%%%%% %%%%%%%%%%% % %%% +% % % % % % % % % +% %%% %%%%% %%%%%%%%% %%%%% % % %%% % +% % % % % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % % % % % % % % +% %%% %%% %%%%% %%% % % %%%%% % %%%%% +% % % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % % % +% % % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % % % % P% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/bigSafeSearch.lay b/search/layouts/bigSafeSearch.lay new file mode 100644 index 0000000..b5fd414 --- /dev/null +++ b/search/layouts/bigSafeSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.%.........%% G % o%%%%.....% +%.%.%%%%%%%.%%%%%% %%%%%%%.%%.% +%............%...%............% +%%%%%...%%%.. ..%.%...%.%%% +%o%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +% ..........Po...%...%. o% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/bigSearch.lay b/search/layouts/bigSearch.lay new file mode 100644 index 0000000..bb59eb8 --- /dev/null +++ b/search/layouts/bigSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.....%.................%.....% +%.%%%.%.%%%.%%%%%%%.%%%.%.....% +%.%...%.%......%......%.%.....% +%...%%%.%.%%%%.%.%%%%...%%%...% +%%%.%.%.%.%......%..%.%...%.%%% +%...%.%%%.%.%%% %%%.%.%%%.%...% +%.%%%.......% %.......%%%.% +%...%.%%%%%.%%%%%%%.%.%%%.%...% +%%%.%...%.%....%....%.%...%.%%% +%...%%%.%.%%%%.%.%%%%.%.%%%...% +%.......%......%......%.....%.% +%.....%.%%%.%%%%%%%.%%%.%.%%%.% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/boxSearch.lay b/search/layouts/boxSearch.lay new file mode 100644 index 0000000..4a113fc --- /dev/null +++ b/search/layouts/boxSearch.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%% +%. . . . . % % +% % % +%. . . . . %G% +% % % +%. . . . . % % +% % % +%. . . . . % % +% P %G% +%. . . . . % % +% % % +%. . . . . % % +% % % +%%%%%%%%%%%%%% diff --git a/search/layouts/capsuleClassic.lay b/search/layouts/capsuleClassic.lay new file mode 100644 index 0000000..06a5c51 --- /dev/null +++ b/search/layouts/capsuleClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%% +%G. G ....% +%.% % %%%%%% %.%%.% +%.%o% % o% %.o%.% +%.%%%.% %%% %..%.% +%..... P %..%G% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/contestClassic.lay b/search/layouts/contestClassic.lay new file mode 100644 index 0000000..84c8733 --- /dev/null +++ b/search/layouts/contestClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%...... G GG%......% +%.%.%%.%% %%%.%%.%.% +%.%....% ooo%.%..%.% +%.%.%%.% %% %.%.%%.% +%o%......P....%....% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/contoursMaze.lay b/search/layouts/contoursMaze.lay new file mode 100644 index 0000000..a068956 --- /dev/null +++ b/search/layouts/contoursMaze.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% % +% P % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/greedySearch.lay b/search/layouts/greedySearch.lay new file mode 100644 index 0000000..4072363 --- /dev/null +++ b/search/layouts/greedySearch.lay @@ -0,0 +1,8 @@ +%%%%%% +%....% +% %%.% +% %%.% +%.P .% +%.%%%% +%....% +%%%%%% \ No newline at end of file diff --git a/search/layouts/mediumClassic.lay b/search/layouts/mediumClassic.lay new file mode 100644 index 0000000..33c5db8 --- /dev/null +++ b/search/layouts/mediumClassic.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%....% +%.%%.%.%%%%%%.%.%%.% +%.%..............%.% +%.%.%%.%% %%.%%.%.% +%......%G G%......% +%.%.%%.%%%%%%.%%.%.% +%.%..............%.% +%.%%.%.%%%%%%.%.%%.% +%....%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/mediumCorners.lay b/search/layouts/mediumCorners.lay new file mode 100644 index 0000000..6a39756 --- /dev/null +++ b/search/layouts/mediumCorners.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/mediumDottedMaze.lay b/search/layouts/mediumDottedMaze.lay new file mode 100644 index 0000000..103f818 --- /dev/null +++ b/search/layouts/mediumDottedMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %% ... % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %% %% %% ... % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% ... % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% ... % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % ... % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% ...... % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/mediumMaze.lay b/search/layouts/mediumMaze.lay new file mode 100644 index 0000000..55c1236 --- /dev/null +++ b/search/layouts/mediumMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/mediumSafeSearch.lay b/search/layouts/mediumSafeSearch.lay new file mode 100644 index 0000000..e7d6b1c --- /dev/null +++ b/search/layouts/mediumSafeSearch.lay @@ -0,0 +1,6 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.% ....%% G %%%%%% o%%.% +%.%o%%%%%%%.%%%%%%% %%%%%.% +% %%%.%%%%%.%%%%%%%.%%%.%.%%%.% +% ..........Po...%.........% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/mediumScaryMaze.lay b/search/layouts/mediumScaryMaze.lay new file mode 100644 index 0000000..65d4c33 --- /dev/null +++ b/search/layouts/mediumScaryMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %%GG % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %%GG %% % +% %% % % % % % %%%%% %%% %%%%%% % +% %% % % % % %% %%%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%% %% %%%%%%% %% %%%%%% % +%%%%%% % % %% %% % +% %%%%%% %% %% %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%% %%%%% %%%%%% % +%%%%%%%% % %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/mediumSearch.lay b/search/layouts/mediumSearch.lay new file mode 100644 index 0000000..2f8af42 --- /dev/null +++ b/search/layouts/mediumSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%%%%............% +%%%.%...%%%.........%.%...%.%%% +%...%%%.%.%%%%.%.%%%%%%.%%%...% +%.%.....%......%......%.....%.% +%.%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/minimaxClassic.lay b/search/layouts/minimaxClassic.lay new file mode 100644 index 0000000..a547397 --- /dev/null +++ b/search/layouts/minimaxClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%%% +%.P G% +% %.%G%%% +%G %%% +%%%%%%%%% diff --git a/search/layouts/oddSearch.lay b/search/layouts/oddSearch.lay new file mode 100644 index 0000000..2ddbc9a --- /dev/null +++ b/search/layouts/oddSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%...%.........%%...% +%.%.%.%%%%%%%%%%.%.% +%..................% +%%%%%%%%.%.%%%%%%%P% +%%%%%%%%....... % +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/openClassic.lay b/search/layouts/openClassic.lay new file mode 100644 index 0000000..6760b42 --- /dev/null +++ b/search/layouts/openClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%%%%%%% +%.. P .... .... % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... G % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... o% +%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/openMaze.lay b/search/layouts/openMaze.lay new file mode 100644 index 0000000..5dee689 --- /dev/null +++ b/search/layouts/openMaze.lay @@ -0,0 +1,23 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% % % +% % % +% % % +% % % +% % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%% +% % % +% % % +% % % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/openSearch.lay b/search/layouts/openSearch.lay new file mode 100644 index 0000000..f02d21d --- /dev/null +++ b/search/layouts/openSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%..................% +%..................% +%........P.........% +%..................% +%..................% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/originalClassic.lay b/search/layouts/originalClassic.lay new file mode 100644 index 0000000..b2770c5 --- /dev/null +++ b/search/layouts/originalClassic.lay @@ -0,0 +1,27 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o%%%%.%%%%%.%%.%%%%%.%%%%o% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%..........................% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%......%%....%%....%%......% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%% %%%% %.%%%%%% +% . %G GG G% . % +%%%%%%.% %%%%%%%%%% %.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%%%%%%%% %.%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o..%%....... .......%%..o% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%......%%....%%....%%......% +%.%%%%%%%%%%.%%.%%%%%%%%%%.% +%.............P............% +%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/powerClassic.lay b/search/layouts/powerClassic.lay new file mode 100644 index 0000000..3f3d983 --- /dev/null +++ b/search/layouts/powerClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%o....o%GGGG%o....o% +%..%...%% %%...%..% +%.%o.%........%.o%.% +%.o%.%.%%%%%%.%.%o.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/smallClassic.lay b/search/layouts/smallClassic.lay new file mode 100644 index 0000000..ce6c1d9 --- /dev/null +++ b/search/layouts/smallClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%......%G G%......% +%.%%...%% %%...%%.% +%.%o.%........%.o%.% +%.%%.%.%%%%%%.%.%%.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/smallMaze.lay b/search/layouts/smallMaze.lay new file mode 100644 index 0000000..72d3ffc --- /dev/null +++ b/search/layouts/smallMaze.lay @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%% +% %% % % % +% %%%%%% % %%%%%% % +%%%%%% P % % +% % %%%%%% %% %%%%% +% %%%% % % % +% %%% %%% % % +%%%%%%%%%% %%%%%% % +%. %% % +%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/search/layouts/smallSafeSearch.lay b/search/layouts/smallSafeSearch.lay new file mode 100644 index 0000000..b97feaa --- /dev/null +++ b/search/layouts/smallSafeSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%% +%.. % G % +%%% %%%%% +% % +%%%%%%% % +% % +% %%%%% % +% % % +%%%%% % % +% %o% +% %%%%%%% +% .% +%%%%%%%.% +%Po .% +%%%%%%%%% diff --git a/search/layouts/smallSearch.lay b/search/layouts/smallSearch.lay new file mode 100644 index 0000000..c2321d4 --- /dev/null +++ b/search/layouts/smallSearch.lay @@ -0,0 +1,5 @@ +%%%%%%%%%%%%%%%%%%%% +%. ...P .% +%.%%.%%.%%.%%.%% %.% +% %% %..... %.% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/testClassic.lay b/search/layouts/testClassic.lay new file mode 100644 index 0000000..4b3ffca --- /dev/null +++ b/search/layouts/testClassic.lay @@ -0,0 +1,10 @@ +%%%%% +% . % +%.G.% +% . % +%. .% +% % +% .% +% % +%P .% +%%%%% diff --git a/search/layouts/testMaze.lay b/search/layouts/testMaze.lay new file mode 100644 index 0000000..4d259a4 --- /dev/null +++ b/search/layouts/testMaze.lay @@ -0,0 +1,3 @@ +%%%%%%%%%% +%. P% +%%%%%%%%%% diff --git a/search/layouts/testSearch.lay b/search/layouts/testSearch.lay new file mode 100644 index 0000000..25bad23 --- /dev/null +++ b/search/layouts/testSearch.lay @@ -0,0 +1,5 @@ +%%%%% +%.P % +%%% % +%. % +%%%%% diff --git a/search/layouts/tinyCorners.lay b/search/layouts/tinyCorners.lay new file mode 100644 index 0000000..526c880 --- /dev/null +++ b/search/layouts/tinyCorners.lay @@ -0,0 +1,8 @@ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% diff --git a/search/layouts/tinyMaze.lay b/search/layouts/tinyMaze.lay new file mode 100644 index 0000000..f7035a5 --- /dev/null +++ b/search/layouts/tinyMaze.lay @@ -0,0 +1,7 @@ +%%%%%%% +% P% +% %%% % +% % % +%% %% +%. %%%% +%%%%%%% diff --git a/search/layouts/tinySafeSearch.lay b/search/layouts/tinySafeSearch.lay new file mode 100644 index 0000000..fea6860 --- /dev/null +++ b/search/layouts/tinySafeSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +% G %...% +%%%%%%% % +%Po % +%.%%.%%.% +%.%%....% +%%%%%%%%% diff --git a/search/layouts/tinySearch.lay b/search/layouts/tinySearch.lay new file mode 100644 index 0000000..c51f4b0 --- /dev/null +++ b/search/layouts/tinySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +%.. ..% +%%%%.%% % +% P % +%.%% %%.% +%.%. .% +%%%%%%%%% diff --git a/search/layouts/trappedClassic.lay b/search/layouts/trappedClassic.lay new file mode 100644 index 0000000..289557f --- /dev/null +++ b/search/layouts/trappedClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%% +% P G% +%G%%%%%% +%.... % +%%%%%%%% diff --git a/search/layouts/trickyClassic.lay b/search/layouts/trickyClassic.lay new file mode 100644 index 0000000..ffa156c --- /dev/null +++ b/search/layouts/trickyClassic.lay @@ -0,0 +1,13 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%.%.....%..%.....%.% +%.%.%%.%% %%.%%.%.% +%...... GGGG%.%....% +%.%....%%%%%%.%..%.% +%.%....% oo%.%..%.% +%.%....% %%%%.%..%.% +%.%...........%..%.% +%.%%.%.%%%%%%.%.%%.% +%o...%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/search/layouts/trickySearch.lay b/search/layouts/trickySearch.lay new file mode 100644 index 0000000..4a607e6 --- /dev/null +++ b/search/layouts/trickySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% diff --git a/search/pacman.py b/search/pacman.py new file mode 100644 index 0000000..740451d --- /dev/null +++ b/search/pacman.py @@ -0,0 +1,684 @@ +# pacman.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +Pacman.py holds the logic for the classic pacman game along with the main +code to run a game. This file is divided into three sections: + + (i) Your interface to the pacman world: + Pacman is a complex environment. You probably don't want to + read through all of the code we wrote to make the game runs + correctly. This section contains the parts of the code + that you will need to understand in order to complete the + project. There is also some code in game.py that you should + understand. + + (ii) The hidden secrets of pacman: + This section contains all of the logic code that the pacman + environment uses to decide who can move where, who dies when + things collide, etc. You shouldn't need to read this section + of code, but you can if you want. + + (iii) Framework to start a game: + The final section contains the code for reading the command + you use to set up the game, then starting up a new game, along with + linking in all the external parts (agent functions, graphics). + Check this section out to see all the options available to you. + +To play your first game, type 'python pacman.py' from the command line. +The keys are 'a', 's', 'd', and 'w' to move (or arrow keys). Have fun! +""" +from game import GameStateData +from game import Game +from game import Directions +from game import Actions +from util import nearestPoint +from util import manhattanDistance +import util, layout +import sys, types, time, random, os + +################################################### +# YOUR INTERFACE TO THE PACMAN WORLD: A GameState # +################################################### + +class GameState: + """ + A GameState specifies the full game state, including the food, capsules, + agent configurations and score changes. + + GameStates are used by the Game object to capture the actual state of the game and + can be used by agents to reason about the game. + + Much of the information in a GameState is stored in a GameStateData object. We + strongly suggest that you access that data via the accessor methods below rather + than referring to the GameStateData object directly. + + Note that in classic Pacman, Pacman is always agent 0. + """ + + #################################################### + # Accessor methods: use these to access state data # + #################################################### + + # static variable keeps track of which states have had getLegalActions called + explored = set() + def getAndResetExplored(): + tmp = GameState.explored.copy() + GameState.explored = set() + return tmp + getAndResetExplored = staticmethod(getAndResetExplored) + + def getLegalActions( self, agentIndex=0 ): + """ + Returns the legal actions for the agent specified. + """ +# GameState.explored.add(self) + if self.isWin() or self.isLose(): return [] + + if agentIndex == 0: # Pacman is moving + return PacmanRules.getLegalActions( self ) + else: + return GhostRules.getLegalActions( self, agentIndex ) + + def generateSuccessor( self, agentIndex, action): + """ + Returns the successor state after the specified agent takes the action. + """ + # Check that successors exist + if self.isWin() or self.isLose(): raise Exception('Can\'t generate a successor of a terminal state.') + + # Copy current state + state = GameState(self) + + # Let agent's logic deal with its action's effects on the board + if agentIndex == 0: # Pacman is moving + state.data._eaten = [False for i in range(state.getNumAgents())] + PacmanRules.applyAction( state, action ) + else: # A ghost is moving + GhostRules.applyAction( state, action, agentIndex ) + + # Time passes + if agentIndex == 0: + state.data.scoreChange += -TIME_PENALTY # Penalty for waiting around + else: + GhostRules.decrementTimer( state.data.agentStates[agentIndex] ) + + # Resolve multi-agent effects + GhostRules.checkDeath( state, agentIndex ) + + # Book keeping + state.data._agentMoved = agentIndex + state.data.score += state.data.scoreChange + GameState.explored.add(self) + GameState.explored.add(state) + return state + + def getLegalPacmanActions( self ): + return self.getLegalActions( 0 ) + + def generatePacmanSuccessor( self, action ): + """ + Generates the successor state after the specified pacman move + """ + return self.generateSuccessor( 0, action ) + + def getPacmanState( self ): + """ + Returns an AgentState object for pacman (in game.py) + + state.pos gives the current position + state.direction gives the travel vector + """ + return self.data.agentStates[0].copy() + + def getPacmanPosition( self ): + return self.data.agentStates[0].getPosition() + + def getGhostStates( self ): + return self.data.agentStates[1:] + + def getGhostState( self, agentIndex ): + if agentIndex == 0 or agentIndex >= self.getNumAgents(): + raise Exception("Invalid index passed to getGhostState") + return self.data.agentStates[agentIndex] + + def getGhostPosition( self, agentIndex ): + if agentIndex == 0: + raise Exception("Pacman's index passed to getGhostPosition") + return self.data.agentStates[agentIndex].getPosition() + + def getGhostPositions(self): + return [s.getPosition() for s in self.getGhostStates()] + + def getNumAgents( self ): + return len( self.data.agentStates ) + + def getScore( self ): + return float(self.data.score) + + def getCapsules(self): + """ + Returns a list of positions (x,y) of the remaining capsules. + """ + return self.data.capsules + + def getNumFood( self ): + return self.data.food.count() + + def getFood(self): + """ + Returns a Grid of boolean food indicator variables. + + Grids can be accessed via list notation, so to check + if there is food at (x,y), just call + + currentFood = state.getFood() + if currentFood[x][y] == True: ... + """ + return self.data.food + + def getWalls(self): + """ + Returns a Grid of boolean wall indicator variables. + + Grids can be accessed via list notation, so to check + if there is a wall at (x,y), just call + + walls = state.getWalls() + if walls[x][y] == True: ... + """ + return self.data.layout.walls + + def hasFood(self, x, y): + return self.data.food[x][y] + + def hasWall(self, x, y): + return self.data.layout.walls[x][y] + + def isLose( self ): + return self.data._lose + + def isWin( self ): + return self.data._win + + ############################################# + # Helper methods: # + # You shouldn't need to call these directly # + ############################################# + + def __init__( self, prevState = None ): + """ + Generates a new state by copying information from its predecessor. + """ + if prevState != None: # Initial state + self.data = GameStateData(prevState.data) + else: + self.data = GameStateData() + + def deepCopy( self ): + state = GameState( self ) + state.data = self.data.deepCopy() + return state + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + return hasattr(other, 'data') and self.data == other.data + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + return hash( self.data ) + + def __str__( self ): + + return str(self.data) + + def initialize( self, layout, numGhostAgents=1000 ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.data.initialize(layout, numGhostAgents) + +############################################################################ +# THE HIDDEN SECRETS OF PACMAN # +# # +# You shouldn't need to look through the code in this section of the file. # +############################################################################ + +SCARED_TIME = 40 # Moves ghosts are scared +COLLISION_TOLERANCE = 0.7 # How close ghosts must be to Pacman to kill +TIME_PENALTY = 1 # Number of points lost each round + +class ClassicGameRules: + """ + These game rules manage the control flow of a game, deciding when + and how the game starts and ends. + """ + def __init__(self, timeout=30): + self.timeout = timeout + + def newGame( self, layout, pacmanAgent, ghostAgents, display, quiet = False, catchExceptions=False): + agents = [pacmanAgent] + ghostAgents[:layout.getNumGhosts()] + initState = GameState() + initState.initialize( layout, len(ghostAgents) ) + game = Game(agents, display, self, catchExceptions=catchExceptions) + game.state = initState + self.initialState = initState.deepCopy() + self.quiet = quiet + return game + + def process(self, state, game): + """ + Checks to see whether it is time to end the game. + """ + if state.isWin(): self.win(state, game) + if state.isLose(): self.lose(state, game) + + def win( self, state, game ): + if not self.quiet: print "Pacman emerges victorious! Score: %d" % state.data.score + game.gameOver = True + + def lose( self, state, game ): + if not self.quiet: print "Pacman died! Score: %d" % state.data.score + game.gameOver = True + + def getProgress(self, game): + return float(game.state.getNumFood()) / self.initialState.getNumFood() + + def agentCrash(self, game, agentIndex): + if agentIndex == 0: + print "Pacman crashed" + else: + print "A ghost crashed" + + def getMaxTotalTime(self, agentIndex): + return self.timeout + + def getMaxStartupTime(self, agentIndex): + return self.timeout + + def getMoveWarningTime(self, agentIndex): + return self.timeout + + def getMoveTimeout(self, agentIndex): + return self.timeout + + def getMaxTimeWarnings(self, agentIndex): + return 0 + +class PacmanRules: + """ + These functions govern how pacman interacts with his environment under + the classic game rules. + """ + PACMAN_SPEED=1 + + def getLegalActions( state ): + """ + Returns a list of possible actions. + """ + return Actions.getPossibleActions( state.getPacmanState().configuration, state.data.layout.walls ) + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action ): + """ + Edits the state to reflect the results of the action. + """ + legal = PacmanRules.getLegalActions( state ) + if action not in legal: + raise Exception("Illegal action " + str(action)) + + pacmanState = state.data.agentStates[0] + + # Update Configuration + vector = Actions.directionToVector( action, PacmanRules.PACMAN_SPEED ) + pacmanState.configuration = pacmanState.configuration.generateSuccessor( vector ) + + # Eat + next = pacmanState.configuration.getPosition() + nearest = nearestPoint( next ) + if manhattanDistance( nearest, next ) <= 0.5 : + # Remove food + PacmanRules.consume( nearest, state ) + applyAction = staticmethod( applyAction ) + + def consume( position, state ): + x,y = position + # Eat food + if state.data.food[x][y]: + state.data.scoreChange += 10 + state.data.food = state.data.food.copy() + state.data.food[x][y] = False + state.data._foodEaten = position + # TODO: cache numFood? + numFood = state.getNumFood() + if numFood == 0 and not state.data._lose: + state.data.scoreChange += 500 + state.data._win = True + # Eat capsule + if( position in state.getCapsules() ): + state.data.capsules.remove( position ) + state.data._capsuleEaten = position + # Reset all ghosts' scared timers + for index in range( 1, len( state.data.agentStates ) ): + state.data.agentStates[index].scaredTimer = SCARED_TIME + consume = staticmethod( consume ) + +class GhostRules: + """ + These functions dictate how ghosts interact with their environment. + """ + GHOST_SPEED=1.0 + def getLegalActions( state, ghostIndex ): + """ + Ghosts cannot stop, and cannot turn around unless they + reach a dead end, but can turn 90 degrees at intersections. + """ + conf = state.getGhostState( ghostIndex ).configuration + possibleActions = Actions.getPossibleActions( conf, state.data.layout.walls ) + reverse = Actions.reverseDirection( conf.direction ) + if Directions.STOP in possibleActions: + possibleActions.remove( Directions.STOP ) + if reverse in possibleActions and len( possibleActions ) > 1: + possibleActions.remove( reverse ) + return possibleActions + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action, ghostIndex): + + legal = GhostRules.getLegalActions( state, ghostIndex ) + if action not in legal: + raise Exception("Illegal ghost action " + str(action)) + + ghostState = state.data.agentStates[ghostIndex] + speed = GhostRules.GHOST_SPEED + if ghostState.scaredTimer > 0: speed /= 2.0 + vector = Actions.directionToVector( action, speed ) + ghostState.configuration = ghostState.configuration.generateSuccessor( vector ) + applyAction = staticmethod( applyAction ) + + def decrementTimer( ghostState): + timer = ghostState.scaredTimer + if timer == 1: + ghostState.configuration.pos = nearestPoint( ghostState.configuration.pos ) + ghostState.scaredTimer = max( 0, timer - 1 ) + decrementTimer = staticmethod( decrementTimer ) + + def checkDeath( state, agentIndex): + pacmanPosition = state.getPacmanPosition() + if agentIndex == 0: # Pacman just moved; Anyone can kill him + for index in range( 1, len( state.data.agentStates ) ): + ghostState = state.data.agentStates[index] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, index ) + else: + ghostState = state.data.agentStates[agentIndex] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, agentIndex ) + checkDeath = staticmethod( checkDeath ) + + def collide( state, ghostState, agentIndex): + if ghostState.scaredTimer > 0: + state.data.scoreChange += 200 + GhostRules.placeGhost(state, ghostState) + ghostState.scaredTimer = 0 + # Added for first-person + state.data._eaten[agentIndex] = True + else: + if not state.data._win: + state.data.scoreChange -= 500 + state.data._lose = True + collide = staticmethod( collide ) + + def canKill( pacmanPosition, ghostPosition ): + return manhattanDistance( ghostPosition, pacmanPosition ) <= COLLISION_TOLERANCE + canKill = staticmethod( canKill ) + + def placeGhost(state, ghostState): + ghostState.configuration = ghostState.start + placeGhost = staticmethod( placeGhost ) + +############################# +# FRAMEWORK TO START A GAME # +############################# + +def default(str): + return str + ' [Default: %default]' + +def parseAgentArgs(str): + if str == None: return {} + pieces = str.split(',') + opts = {} + for p in pieces: + if '=' in p: + key, val = p.split('=') + else: + key,val = p, 1 + opts[key] = val + return opts + +def readCommand( argv ): + """ + Processes the command used to run pacman from the command line. + """ + from optparse import OptionParser + usageStr = """ + USAGE: python pacman.py + EXAMPLES: (1) python pacman.py + - starts an interactive game + (2) python pacman.py --layout smallClassic --zoom 2 + OR python pacman.py -l smallClassic -z 2 + - starts an interactive game on a smaller board, zoomed in + """ + parser = OptionParser(usageStr) + + parser.add_option('-n', '--numGames', dest='numGames', type='int', + help=default('the number of GAMES to play'), metavar='GAMES', default=1) + parser.add_option('-l', '--layout', dest='layout', + help=default('the LAYOUT_FILE from which to load the map layout'), + metavar='LAYOUT_FILE', default='mediumClassic') + parser.add_option('-p', '--pacman', dest='pacman', + help=default('the agent TYPE in the pacmanAgents module to use'), + metavar='TYPE', default='KeyboardAgent') + parser.add_option('-t', '--textGraphics', action='store_true', dest='textGraphics', + help='Display output as text only', default=False) + parser.add_option('-q', '--quietTextGraphics', action='store_true', dest='quietGraphics', + help='Generate minimal output and no graphics', default=False) + parser.add_option('-g', '--ghosts', dest='ghost', + help=default('the ghost agent TYPE in the ghostAgents module to use'), + metavar = 'TYPE', default='RandomGhost') + parser.add_option('-k', '--numghosts', type='int', dest='numGhosts', + help=default('The maximum number of ghosts to use'), default=4) + parser.add_option('-z', '--zoom', type='float', dest='zoom', + help=default('Zoom the size of the graphics window'), default=1.0) + parser.add_option('-f', '--fixRandomSeed', action='store_true', dest='fixRandomSeed', + help='Fixes the random seed to always play the same game', default=False) + parser.add_option('-r', '--recordActions', action='store_true', dest='record', + help='Writes game histories to a file (named by the time they were played)', default=False) + parser.add_option('--replay', dest='gameToReplay', + help='A recorded game file (pickle) to replay', default=None) + parser.add_option('-a','--agentArgs',dest='agentArgs', + help='Comma separated values sent to agent. e.g. "opt1=val1,opt2,opt3=val3"') + parser.add_option('-x', '--numTraining', dest='numTraining', type='int', + help=default('How many episodes are training (suppresses output)'), default=0) + parser.add_option('--frameTime', dest='frameTime', type='float', + help=default('Time to delay between frames; <0 means keyboard'), default=0.1) + parser.add_option('-c', '--catchExceptions', action='store_true', dest='catchExceptions', + help='Turns on exception handling and timeouts during games', default=False) + parser.add_option('--timeout', dest='timeout', type='int', + help=default('Maximum length of time an agent can spend computing in a single game'), default=30) + + options, otherjunk = parser.parse_args(argv) + if len(otherjunk) != 0: + raise Exception('Command line input not understood: ' + str(otherjunk)) + args = dict() + + # Fix the random seed + if options.fixRandomSeed: random.seed('cs188') + + # Choose a layout + args['layout'] = layout.getLayout( options.layout ) + if args['layout'] == None: raise Exception("The layout " + options.layout + " cannot be found") + + # Choose a Pacman agent + noKeyboard = options.gameToReplay == None and (options.textGraphics or options.quietGraphics) + pacmanType = loadAgent(options.pacman, noKeyboard) + agentOpts = parseAgentArgs(options.agentArgs) + if options.numTraining > 0: + args['numTraining'] = options.numTraining + if 'numTraining' not in agentOpts: agentOpts['numTraining'] = options.numTraining + pacman = pacmanType(**agentOpts) # Instantiate Pacman with agentArgs + args['pacman'] = pacman + + # Don't display training games + if 'numTrain' in agentOpts: + options.numQuiet = int(agentOpts['numTrain']) + options.numIgnore = int(agentOpts['numTrain']) + + # Choose a ghost agent + ghostType = loadAgent(options.ghost, noKeyboard) + args['ghosts'] = [ghostType( i+1 ) for i in range( options.numGhosts )] + + # Choose a display format + if options.quietGraphics: + import textDisplay + args['display'] = textDisplay.NullGraphics() + elif options.textGraphics: + import textDisplay + textDisplay.SLEEP_TIME = options.frameTime + args['display'] = textDisplay.PacmanGraphics() + else: + import graphicsDisplay + args['display'] = graphicsDisplay.PacmanGraphics(options.zoom, frameTime = options.frameTime) + args['numGames'] = options.numGames + args['record'] = options.record + args['catchExceptions'] = options.catchExceptions + args['timeout'] = options.timeout + + # Special case: recorded games don't use the runGames method or args structure + if options.gameToReplay != None: + print 'Replaying recorded game %s.' % options.gameToReplay + import cPickle + f = open(options.gameToReplay) + try: recorded = cPickle.load(f) + finally: f.close() + recorded['display'] = args['display'] + replayGame(**recorded) + sys.exit(0) + + return args + +def loadAgent(pacman, nographics): + # Looks through all pythonPath Directories for the right module, + pythonPathStr = os.path.expandvars("$PYTHONPATH") + if pythonPathStr.find(';') == -1: + pythonPathDirs = pythonPathStr.split(':') + else: + pythonPathDirs = pythonPathStr.split(';') + pythonPathDirs.append('.') + + for moduleDir in pythonPathDirs: + if not os.path.isdir(moduleDir): continue + moduleNames = [f for f in os.listdir(moduleDir) if f.endswith('gents.py')] + for modulename in moduleNames: + try: + module = __import__(modulename[:-3]) + except ImportError: + continue + if pacman in dir(module): + if nographics and modulename == 'keyboardAgents.py': + raise Exception('Using the keyboard requires graphics (not text display)') + return getattr(module, pacman) + raise Exception('The agent ' + pacman + ' is not specified in any *Agents.py.') + +def replayGame( layout, actions, display ): + import pacmanAgents, ghostAgents + rules = ClassicGameRules() + agents = [pacmanAgents.GreedyAgent()] + [ghostAgents.RandomGhost(i+1) for i in range(layout.getNumGhosts())] + game = rules.newGame( layout, agents[0], agents[1:], display ) + state = game.state + display.initialize(state.data) + + for action in actions: + # Execute the action + state = state.generateSuccessor( *action ) + # Change the display + display.update( state.data ) + # Allow for game specific conditions (winning, losing, etc.) + rules.process(state, game) + + display.finish() + +def runGames( layout, pacman, ghosts, display, numGames, record, numTraining = 0, catchExceptions=False, timeout=30 ): + import __main__ + __main__.__dict__['_display'] = display + + rules = ClassicGameRules(timeout) + games = [] + + for i in range( numGames ): + beQuiet = i < numTraining + if beQuiet: + # Suppress output and graphics + import textDisplay + gameDisplay = textDisplay.NullGraphics() + rules.quiet = True + else: + gameDisplay = display + rules.quiet = False + game = rules.newGame( layout, pacman, ghosts, gameDisplay, beQuiet, catchExceptions) + game.run() + if not beQuiet: games.append(game) + + if record: + import time, cPickle + fname = ('recorded-game-%d' % (i + 1)) + '-'.join([str(t) for t in time.localtime()[1:6]]) + f = file(fname, 'w') + components = {'layout': layout, 'actions': game.moveHistory} + cPickle.dump(components, f) + f.close() + + if (numGames-numTraining) > 0: + scores = [game.state.getScore() for game in games] + wins = [game.state.isWin() for game in games] + winRate = wins.count(True)/ float(len(wins)) + print 'Average Score:', sum(scores) / float(len(scores)) + print 'Scores: ', ', '.join([str(score) for score in scores]) + print 'Win Rate: %d/%d (%.2f)' % (wins.count(True), len(wins), winRate) + print 'Record: ', ', '.join([ ['Loss', 'Win'][int(w)] for w in wins]) + + return games + +if __name__ == '__main__': + """ + The main function called when pacman.py is run + from the command line: + + > python pacman.py + + See the usage string for more details. + + > python pacman.py --help + """ + args = readCommand( sys.argv[1:] ) # Get game components based on input + runGames( **args ) + + # import cProfile + # cProfile.run("runGames( **args )") + pass diff --git a/search/pacman.pyc b/search/pacman.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f49bcf5613893ab4aa5ed35f17b83f351238f4a7 GIT binary patch literal 27197 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 YYma=T*=j!1e6+dTo@+l&+-!LN2RyLO8~^|S literal 0 HcmV?d00001 diff --git a/search/searchAgents.py b/search/searchAgents.py new file mode 100644 index 0000000..f97fe05 --- /dev/null +++ b/search/searchAgents.py @@ -0,0 +1,576 @@ +# searchAgents.py +# --------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +This file contains all of the agents that can be selected to control Pacman. To +select an agent, use the '-p' option when running pacman.py. Arguments can be +passed to your agent using '-a'. For example, to load a SearchAgent that uses +depth first search (dfs), run the following command: + +> python pacman.py -p SearchAgent -a fn=depthFirstSearch + +Commands to invoke other search strategies can be found in the project +description. + +Please only change the parts of the file you are asked to. Look for the lines +that say + +"*** YOUR CODE HERE ***" + +The parts you fill in start about 3/4 of the way down. Follow the project +description for details. + +Good luck and happy searching! +""" + +from game import Directions +from game import Agent +from game import Actions +import util +import time +import search + +class GoWestAgent(Agent): + "An agent that goes West until it can't." + + def getAction(self, state): + "The agent receives a GameState (defined in pacman.py)." + if Directions.WEST in state.getLegalPacmanActions(): + return Directions.WEST + else: + return Directions.STOP + +####################################################### +# This portion is written for you, but will only work # +# after you fill in parts of search.py # +####################################################### + +class SearchAgent(Agent): + """ + This very general search agent finds a path using a supplied search + algorithm for a supplied search problem, then returns actions to follow that + path. + + As a default, this agent runs DFS on a PositionSearchProblem to find + location (1,1) + + Options for fn include: + depthFirstSearch or dfs + breadthFirstSearch or bfs + + + Note: You should NOT change any code in SearchAgent + """ + + def __init__(self, fn='depthFirstSearch', prob='PositionSearchProblem', heuristic='nullHeuristic'): + # Warning: some advanced Python magic is employed below to find the right functions and problems + + # Get the search function from the name and heuristic + if fn not in dir(search): + raise AttributeError, fn + ' is not a search function in search.py.' + func = getattr(search, fn) + if 'heuristic' not in func.func_code.co_varnames: + print('[SearchAgent] using function ' + fn) + self.searchFunction = func + else: + if heuristic in globals().keys(): + heur = globals()[heuristic] + elif heuristic in dir(search): + heur = getattr(search, heuristic) + else: + raise AttributeError, heuristic + ' is not a function in searchAgents.py or search.py.' + print('[SearchAgent] using function %s and heuristic %s' % (fn, heuristic)) + # Note: this bit of Python trickery combines the search algorithm and the heuristic + self.searchFunction = lambda x: func(x, heuristic=heur) + + # Get the search problem type from the name + if prob not in globals().keys() or not prob.endswith('Problem'): + raise AttributeError, prob + ' is not a search problem type in SearchAgents.py.' + self.searchType = globals()[prob] + print('[SearchAgent] using problem type ' + prob) + + def registerInitialState(self, state): + """ + This is the first time that the agent sees the layout of the game + board. Here, we choose a path to the goal. In this phase, the agent + should compute the path to the goal and store it in a local variable. + All of the work is done in this method! + + state: a GameState object (pacman.py) + """ + if self.searchFunction == None: raise Exception, "No search function provided for SearchAgent" + starttime = time.time() + problem = self.searchType(state) # Makes a new search problem + self.actions = self.searchFunction(problem) # Find a path + totalCost = problem.getCostOfActions(self.actions) + print('Path found with total cost of %d in %.1f seconds' % (totalCost, time.time() - starttime)) + if '_expanded' in dir(problem): print('Search nodes expanded: %d' % problem._expanded) + + def getAction(self, state): + """ + Returns the next action in the path chosen earlier (in + registerInitialState). Return Directions.STOP if there is no further + action to take. + + state: a GameState object (pacman.py) + """ + if 'actionIndex' not in dir(self): self.actionIndex = 0 + i = self.actionIndex + self.actionIndex += 1 + if i < len(self.actions): + return self.actions[i] + else: + return Directions.STOP + +class PositionSearchProblem(search.SearchProblem): + """ + A search problem defines the state space, start state, goal test, successor + function and cost function. This search problem can be used to find paths + to a particular point on the pacman board. + + The state space consists of (x,y) positions in a pacman game. + + Note: this search problem is fully specified; you should NOT change it. + """ + + def __init__(self, gameState, costFn = lambda x: 1, goal=(1,1), start=None, warn=True, visualize=True): + """ + Stores the start and goal. + + gameState: A GameState object (pacman.py) + costFn: A function from a search state (tuple) to a non-negative number + goal: A position in the gameState + """ + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + if start != None: self.startState = start + self.goal = goal + self.costFn = costFn + self.visualize = visualize + if warn and (gameState.getNumFood() != 1 or not gameState.hasFood(*goal)): + print 'Warning: this does not look like a regular search maze' + + # For display purposes + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def getStartState(self): + return self.startState + + def isGoalState(self, state): + isGoal = state == self.goal + + # For display purposes only + if isGoal and self.visualize: + self._visitedlist.append(state) + import __main__ + if '_display' in dir(__main__): + if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable + __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable + + return isGoal + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, + (successor, action, stepCost), where 'successor' is a + successor to the current state, 'action' is the action + required to get there, and 'stepCost' is the incremental + cost of expanding to that successor + """ + + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextState = (nextx, nexty) + cost = self.costFn(nextState) + successors.append( ( nextState, action, cost) ) + + # Bookkeeping for display purposes + self._expanded += 1 # DO NOT CHANGE + if state not in self._visited: + self._visited[state] = True + self._visitedlist.append(state) + + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. + """ + if actions == None: return 999999 + x,y= self.getStartState() + cost = 0 + for action in actions: + # Check figure out the next state and see whether its' legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + cost += self.costFn((x,y)) + return cost + +class StayEastSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the West side of the board. + + The cost function for stepping into a position (x,y) is 1/2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn, (1, 1), None, False) + +class StayWestSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the East side of the board. + + The cost function for stepping into a position (x,y) is 2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: 2 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn) + +def manhattanHeuristic(position, problem, info={}): + "The Manhattan distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) + +def euclideanHeuristic(position, problem, info={}): + "The Euclidean distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 + +##################################################### +# This portion is incomplete. Time to write code! # +##################################################### + +class CornersProblem(search.SearchProblem): + """ + This search problem finds paths through all four corners of a layout. + + You must select a suitable state space and successor function + """ + + def __init__(self, startingGameState): + """ + Stores the walls, pacman's starting position and corners. + """ + self.walls = startingGameState.getWalls() + self.startingPosition = startingGameState.getPacmanPosition() + top, right = self.walls.height-2, self.walls.width-2 + self.corners = ((1,1), (1,top), (right, 1), (right, top)) + for corner in self.corners: + if not startingGameState.hasFood(*corner): + print 'Warning: no food in corner ' + str(corner) + self._expanded = 0 # DO NOT CHANGE; Number of search nodes expanded + # Please add any code here which you would like to use + # in initializing the problem + "*** YOUR CODE HERE ***" + + def getStartState(self): + """ + Returns the start state (in your state space, not the full Pacman state + space) + """ + "*** YOUR CODE HERE ***" + return (self.startingPosition,[]) + util.raiseNotDefined() + + def isGoalState(self, state): + """ + Returns whether this search state is a goal state of the problem. + """ + "*** YOUR CODE HERE ***" + pos = state[0] + Visited_Corners = state[1] + + return len(Visited_Corners)==4 + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' + is the incremental cost of expanding to that successor + """ + x,y = state[0] + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + # Add a successor state to the successor list if the action is legal + # Here's a code snippet for figuring out whether a new position hits a wall: + # x,y = currentPosition + # dx, dy = Actions.directionToVector(action) + # nextx, nexty = int(x + dx), int(y + dy) + # hitsWall = self.walls[nextx][nexty] + + "" + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + next_node = (nextx, nexty) + hitsWall = self.walls[nextx][nexty] + if not hitsWall: + list_vis_corner = list(state[1]) + if next_node in self.corners: + if next_node not in list_vis_corner: + list_vis_corner.append( next_node ) + successor = ((next_node, list_vis_corner), action, 1) + successors.append(successor) + self._expanded += 1 # DO NOT CHANGE + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. This is implemented for you. + """ + if actions == None: return 999999 + x,y= self.startingPosition + for action in actions: + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + return len(actions) + +def minmanhattan(corners, pos): + if len(corners) == 0: + return 0 + + hn = [] + for loc in corners: + dis = abs(loc[0] - pos[0]) + abs(loc[1] - pos[1])+ minmanhattan([c for c in corners if c != loc], loc) + hn.append(dis) + + return min(hn) +def cornersHeuristic(state, problem): + """ + A heuristic for the CornersProblem that you defined. + + state: The current search state + (a data structure you chose in your search problem) + + problem: The CornersProblem instance for this layout. + + This function should always return a number that is a lower bound on the + shortest path from the state to a goal of the problem; i.e. it should be + admissible (as well as consistent). + """ + corners = problem.corners # These are the corner coordinates + walls = problem.walls # These are the walls of the maze, as a Grid (game.py) + + "*** YOUR CODE HERE ***" + return minmanhattan([item for item in corners if item not in state[1]],state[0]) + return 0 # Default to trivial solution + +class AStarCornersAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic) + self.searchType = CornersProblem + +class FoodSearchProblem: + """ + A search problem associated with finding the a path that collects all of the + food (dots) in a Pacman game. + + A search state in this problem is a tuple ( pacmanPosition, foodGrid ) where + pacmanPosition: a tuple (x,y) of integers specifying Pacman's position + foodGrid: a Grid (see game.py) of either True or False, specifying remaining food + """ + def __init__(self, startingGameState): + self.start = (startingGameState.getPacmanPosition(), startingGameState.getFood()) + self.walls = startingGameState.getWalls() + self.startingGameState = startingGameState + self._expanded = 0 # DO NOT CHANGE + self.heuristicInfo = {} # A dictionary for the heuristic to store information + + def getStartState(self): + return self.start + + def isGoalState(self, state): + return state[1].count() == 0 + + def getSuccessors(self, state): + "Returns successor states, the actions they require, and a cost of 1." + successors = [] + self._expanded += 1 # DO NOT CHANGE + for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state[0] + dx, dy = Actions.directionToVector(direction) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextFood = state[1].copy() + nextFood[nextx][nexty] = False + successors.append( ( ((nextx, nexty), nextFood), direction, 1) ) + return successors + + def getCostOfActions(self, actions): + """Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999""" + x,y= self.getStartState()[0] + cost = 0 + for action in actions: + # figure out the next state and see whether it's legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: + return 999999 + cost += 1 + return cost + +class AStarFoodSearchAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, foodHeuristic) + self.searchType = FoodSearchProblem + +def foodHeuristic(state, problem): + """ + Your heuristic for the FoodSearchProblem goes here. + + This heuristic must be consistent to ensure correctness. First, try to come + up with an admissible heuristic; almost all admissible heuristics will be + consistent as well. + + If using A* ever finds a solution that is worse uniform cost search finds, + your heuristic is *not* consistent, and probably not admissible! On the + other hand, inadmissible or inconsistent heuristics may find optimal + solutions, so be careful. + + The state is a tuple ( pacmanPosition, foodGrid ) where foodGrid is a Grid + (see game.py) of either True or False. You can call foodGrid.asList() to get + a list of food coordinates instead. + + If you want access to info like walls, capsules, etc., you can query the + problem. For example, problem.walls gives you a Grid of where the walls + are. + + If you want to *store* information to be reused in other calls to the + heuristic, there is a dictionary called problem.heuristicInfo that you can + use. For example, if you only want to count the walls once and store that + value, try: problem.heuristicInfo['wallCount'] = problem.walls.count() + Subsequent calls to this heuristic can access + problem.heuristicInfo['wallCount'] + """ + + "*** YOUR CODE HERE ***" + position, foodGrid = state + food_coordinates = foodGrid.asList() + if not food_coordinates: + return 0 + res = -1 + for tmp in food_coordinates: + dist = mazeDistance(tmp, position, problem.startingGameState) + res = max(res,dist) + return res + return 0 + +class ClosestDotSearchAgent(SearchAgent): + "Search for all food using a sequence of searches" + def registerInitialState(self, state): + self.actions = [] + currentState = state + while(currentState.getFood().count() > 0): + nextPathSegment = self.findPathToClosestDot(currentState) # The missing piece + self.actions += nextPathSegment + for action in nextPathSegment: + legal = currentState.getLegalActions() + if action not in legal: + t = (str(action), str(currentState)) + raise Exception, 'findPathToClosestDot returned an illegal move: %s!\n%s' % t + currentState = currentState.generateSuccessor(0, action) + self.actionIndex = 0 + print 'Path found with cost %d.' % len(self.actions) + + def findPathToClosestDot(self, gameState): + """ + Returns a path (a list of actions) to the closest dot, starting from + gameState. + """ + # Here are some useful elements of the startState + startPosition = gameState.getPacmanPosition() + food = gameState.getFood() + walls = gameState.getWalls() + problem = AnyFoodSearchProblem(gameState) + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +class AnyFoodSearchProblem(PositionSearchProblem): + """ + A search problem for finding a path to any food. + + This search problem is just like the PositionSearchProblem, but has a + different goal test, which you need to fill in below. The state space and + successor function do not need to be changed. + + The class definition above, AnyFoodSearchProblem(PositionSearchProblem), + inherits the methods of the PositionSearchProblem. + + You can use this search problem to help you fill in the findPathToClosestDot + method. + """ + + def __init__(self, gameState): + "Stores information from the gameState. You don't need to change this." + # Store the food for later reference + self.food = gameState.getFood() + + # Store info for the PositionSearchProblem (no need to change this) + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + self.costFn = lambda x: 1 + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def isGoalState(self, state): + """ + The state is Pacman's position. Fill this in with a goal test that will + complete the problem definition. + """ + x,y = state + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +def mazeDistance(point1, point2, gameState): + """ + Returns the maze distance between any two points, using the search functions + you have already built. The gameState can be any game state -- Pacman's + position in that state is ignored. + + Example usage: mazeDistance( (2,4), (5,6), gameState) + + This might be a useful helper function for your ApproximateSearchAgent. + """ + x1, y1 = point1 + x2, y2 = point2 + walls = gameState.getWalls() + assert not walls[x1][y1], 'point1 is a wall: ' + str(point1) + assert not walls[x2][y2], 'point2 is a wall: ' + str(point2) + prob = PositionSearchProblem(gameState, start=point1, goal=point2, warn=False, visualize=False) + return len(search.bfs(prob)) diff --git a/search/searchAgents.pyc b/search/searchAgents.pyc new file mode 100644 index 0000000000000000000000000000000000000000..2d07b23ba986aff3beb140d75acb4f548c3d4752 GIT binary patch literal 22415 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 literal 0 HcmV?d00001 diff --git a/search/searchTestClasses.py b/search/searchTestClasses.py new file mode 100644 index 0000000..1e985a2 --- /dev/null +++ b/search/searchTestClasses.py @@ -0,0 +1,821 @@ +# searchTestClasses.py +# -------------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import re +import testClasses +import textwrap + +# import project specific code +import layout +import pacman +from search import SearchProblem + +# helper function for printing solutions in solution files +def wrap_solution(solution): + if type(solution) == type([]): + return '\n'.join(textwrap.wrap(' '.join(solution))) + else: + return str(solution) + + + + +def followAction(state, action, problem): + for successor1, action1, cost1 in problem.getSuccessors(state): + if action == action1: return successor1 + return None + +def followPath(path, problem): + state = problem.getStartState() + states = [state] + for action in path: + state = followAction(state, action, problem) + states.append(state) + return states + +def checkSolution(problem, path): + state = problem.getStartState() + for action in path: + state = followAction(state, action, problem) + return problem.isGoalState(state) + +# Search problem on a plain graph +class GraphSearch(SearchProblem): + + # Read in the state graph; define start/end states, edges and costs + def __init__(self, graph_text): + self.expanded_states = [] + lines = graph_text.split('\n') + r = re.match('start_state:(.*)', lines[0]) + if r == None: + print "Broken graph:" + print '"""%s"""' % graph_text + raise Exception("GraphSearch graph specification start_state not found or incorrect on line:" + l) + self.start_state = r.group(1).strip() + r = re.match('goal_states:(.*)', lines[1]) + if r == None: + print "Broken graph:" + print '"""%s"""' % graph_text + raise Exception("GraphSearch graph specification goal_states not found or incorrect on line:" + l) + goals = r.group(1).split() + self.goals = map(str.strip, goals) + self.successors = {} + all_states = set() + self.orderedSuccessorTuples = [] + for l in lines[2:]: + if len(l.split()) == 3: + start, action, next_state = l.split() + cost = 1 + elif len(l.split()) == 4: + start, action, next_state, cost = l.split() + else: + print "Broken graph:" + print '"""%s"""' % graph_text + raise Exception("Invalid line in GraphSearch graph specification on line:" + l) + cost = float(cost) + self.orderedSuccessorTuples.append((start, action, next_state, cost)) + all_states.add(start) + all_states.add(next_state) + if start not in self.successors: + self.successors[start] = [] + self.successors[start].append((next_state, action, cost)) + for s in all_states: + if s not in self.successors: + self.successors[s] = [] + + # Get start state + def getStartState(self): + return self.start_state + + # Check if a state is a goal state + def isGoalState(self, state): + return state in self.goals + + # Get all successors of a state + def getSuccessors(self, state): + self.expanded_states.append(state) + return list(self.successors[state]) + + # Calculate total cost of a sequence of actions + def getCostOfActions(self, actions): + total_cost = 0 + state = self.start_state + for a in actions: + successors = self.successors[state] + match = False + for (next_state, action, cost) in successors: + if a == action: + state = next_state + total_cost += cost + match = True + if not match: + print 'invalid action sequence' + sys.exit(1) + return total_cost + + # Return a list of all states on which 'getSuccessors' was called + def getExpandedStates(self): + return self.expanded_states + + def __str__(self): + print self.successors + edges = ["%s %s %s %s" % t for t in self.orderedSuccessorTuples] + return \ +"""start_state: %s +goal_states: %s +%s""" % (self.start_state, " ".join(self.goals), "\n".join(edges)) + + + +def parseHeuristic(heuristicText): + heuristic = {} + for line in heuristicText.split('\n'): + tokens = line.split() + if len(tokens) != 2: + print "Broken heuristic:" + print '"""%s"""' % graph_text + raise Exception("GraphSearch heuristic specification broken:" + l) + state, h = tokens + heuristic[state] = float(h) + + def graphHeuristic(state, problem=None): + if state in heuristic: + return heuristic[state] + else: + pp = pprint.PrettyPrinter(indent=4) + print "Heuristic:" + pp.pprint(heuristic) + raise Exception("Graph heuristic called with invalid state: " + str(state)) + + return graphHeuristic + + +class GraphSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(GraphSearchTest, self).__init__(question, testDict) + self.graph_text = testDict['graph'] + self.alg = testDict['algorithm'] + self.diagram = testDict['diagram'] + self.exactExpansionOrder = testDict.get('exactExpansionOrder', 'True').lower() == "true" + if 'heuristic' in testDict: + self.heuristic = parseHeuristic(testDict['heuristic']) + else: + self.heuristic = None + + # Note that the return type of this function is a tripple: + # (solution, expanded states, error message) + def getSolInfo(self, search): + alg = getattr(search, self.alg) + problem = GraphSearch(self.graph_text) + if self.heuristic != None: + solution = alg(problem, self.heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + return solution, problem.getExpandedStates(), None + + # Run student code. If an error message is returned, print error and return false. + # If a good solution is returned, printn the solution and return true; otherwise, + # print both the correct and student's solution and return false. + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded_states = [str.split(solutionDict['expanded_states']), str.split(solutionDict['rev_expanded_states'])] + + solution, expanded_states, error = self.getSolInfo(search) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\t%s' % error) + return False + + if solution in gold_solution and (not self.exactExpansionOrder or expanded_states in gold_expanded_states): + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tsolution:\t\t%s' % solution) + grades.addMessage('\texpanded_states:\t%s' % expanded_states) + return True + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tgraph:') + for line in self.diagram.split('\n'): + grades.addMessage('\t %s' % (line,)) + grades.addMessage('\tstudent solution:\t\t%s' % solution) + grades.addMessage('\tstudent expanded_states:\t%s' % expanded_states) + grades.addMessage('') + grades.addMessage('\tcorrect solution:\t\t%s' % gold_solution[0]) + grades.addMessage('\tcorrect expanded_states:\t%s' % gold_expanded_states[0]) + grades.addMessage('\tcorrect rev_solution:\t\t%s' % gold_solution[1]) + grades.addMessage('\tcorrect rev_expanded_states:\t%s' % gold_expanded_states[1]) + return False + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + + # write forward solution + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: "%s"\n' % ' '.join(solution)) + handle.write('expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # reverse and write backwards solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: "%s"\n' % ' '.join(solution)) + handle.write('rev_expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + + +class PacmanSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(PacmanSearchTest, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + self.alg = testDict['algorithm'] + self.layoutName = testDict['layoutName'] + + # TODO: sensible to have defaults like this? + self.leewayFactor = float(testDict.get('leewayFactor', '1')) + self.costFn = eval(testDict.get('costFn', 'None')) + self.searchProblemClassName = testDict.get('searchProblemClass', 'PositionSearchProblem') + self.heuristicName = testDict.get('heuristic', None) + + + def getSolInfo(self, search, searchAgents): + alg = getattr(search, self.alg) + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + + problemClass = getattr(searchAgents, self.searchProblemClassName) + problemOptions = {} + if self.costFn != None: + problemOptions['costFn'] = self.costFn + problem = problemClass(start_state, **problemOptions) + heuristic = getattr(searchAgents, self.heuristicName) if self.heuristicName != None else None + + if heuristic != None: + solution = alg(problem, heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + from game import Directions + dirs = Directions.LEFT.keys() + if [el in dirs for el in solution].count(False) != 0: + return None, None, 'Output of %s must be a list of actions from game.Directions' % self.alg + + expanded = problem._expanded + return solution, expanded, None + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded = max(int(solutionDict['expanded_nodes']), int(solutionDict['rev_expanded_nodes'])) + + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % error) + return False + + # FIXME: do we want to standardize test output format? + + if solution not in gold_solution: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Solution not correct.') + grades.addMessage('\tstudent solution length: %s' % len(solution)) + grades.addMessage('\tstudent solution:\n%s' % wrap_solution(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length: %s' % len(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution length: %s' % len(gold_solution[1])) + grades.addMessage('\tcorrect solution:\n%s' % wrap_solution(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution:\n%s' % wrap_solution(gold_solution[1])) + return False + + if expanded > self.leewayFactor * gold_expanded and expanded > gold_expanded + 1: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Too many node expanded; are you expanding nodes twice?') + grades.addMessage('\tstudent nodes expanded: %s' % expanded) + grades.addMessage('') + grades.addMessage('\tcorrect nodes expanded: %s (leewayFactor %s)' % (gold_expanded, self.leewayFactor)) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length: %s' % len(solution)) + grades.addMessage('\tnodes expanded:\t\t%s' % expanded) + return True + + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + handle.write('# Number of nodes expanded must be with a factor of %s of the numbers below.\n' % self.leewayFactor) + + # write forward solution + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('expanded_nodes: "%s"\n' % expanded) + + # write backward solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('rev_expanded_nodes: "%s"\n' % expanded) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + +from game import Actions +def getStatesFromPath(start, path): + "Returns the list of states visited along the path" + vis = [start] + curr = start + for a in path: + x,y = curr + dx, dy = Actions.directionToVector(a) + curr = (int(x + dx), int(y + dy)) + vis.append(curr) + return vis + +class CornerProblemTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerProblemTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, search, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problem = searchAgents.CornersProblem(gameState) + path = search.bfs(problem) + + gameState = pacman.GameState() + gameState.initialize(lay, 0) + visited = getStatesFromPath(gameState.getPacmanPosition(), path) + top, right = gameState.getWalls().height-2, gameState.getWalls().width-2 + missedCorners = [p for p in ((1,1), (1,top), (right, 1), (right, top)) if p not in visited] + + return path, missedCorners + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution, missedCorners = self.solution(search, searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('The result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(missedCorners) != 0: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Corners missed: %s' % missedCorners) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Optimal solution not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print "Solving problem", self.layoutName + print self.layoutText + + path, _ = self.solution(search, searchAgents) + length = len(path) + print "Problem solved" + + handle.write('solution_length: "%s"\n' % length) + handle.close() + + + + +# template = """class: "HeuristicTest" +# +# heuristic: "foodHeuristic" +# searchProblemClass: "FoodSearchProblem" +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(doneTests + foodTests): +# f = open("food_heuristic_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class HeuristicTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + def checkHeuristic(self, heuristic, problem, state, solutionCost): + h0 = heuristic(state, problem) + + if solutionCost == 0: + if h0 == 0: + return True, '' + else: + return False, 'Heuristic failed H(goal) == 0 test' + + if h0 < 0: + return False, 'Heuristic failed H >= 0 test' + if not h0 > 0: + return False, 'Heuristic failed non-triviality test' + if not h0 <= solutionCost: + return False, 'Heuristic failed admissibility test' + + for succ, action, stepCost in problem.getSuccessors(state): + h1 = heuristic(succ, problem) + if h1 < 0: return False, 'Heuristic failed H >= 0 test' + if h0 - h1 > stepCost: return False, 'Heuristic failed consistency test' + + return True, '' + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + solutionCost = int(solutionDict['solution_cost']) + problem, state, heuristic = self.setupProblem(searchAgents) + + passed, message = self.checkHeuristic(heuristic, problem, state, solutionCost) + + if not passed: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % message) + return False + else: + grades.addMessage('PASS: %s' % self.path) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print "Solving problem", self.layoutName, self.heuristicName + print self.layoutText + problem, _, heuristic = self.setupProblem(searchAgents) + path = search.astar(problem, heuristic) + cost = problem.getCostOfActions(path) + print "Problem solved" + + handle.write('solution_cost: "%s"\n' % cost) + handle.close() + return True + + + + + + +class HeuristicGrade(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicGrade, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + self.basePoints = int(testDict['basePoints']) + self.thresholds = [int(t) for t in testDict['gradingThresholds'].split()] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + problem, _, heuristic = self.setupProblem(searchAgents) + + path = search.astar(problem, heuristic) + + expanded = problem._expanded + + if not checkSolution(problem, path): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tReturned path is not a solution.') + grades.addMessage('\tpath returned by astar: %s' % expanded) + return False + + grades.addPoints(self.basePoints) + points = 0 + for threshold in self.thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(self.thresholds): + grades.addMessage('PASS: %s' % self.path) + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\texpanded nodes: %s' % expanded) + grades.addMessage('\tthresholds: %s' % self.thresholds) + + return True + + + def writeSolution(self, moduleDict, filePath): + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# File intentionally blank.\n') + handle.close() + return True + + + + + +# template = """class: "ClosestDotTest" +# +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(foodTests): +# f = open("closest_dot_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class ClosestDotTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(ClosestDotTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + path = searchAgents.ClosestDotSearchAgent().findPathToClosestDot(gameState) + return path + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution = self.solution(searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tThe result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Closest dot not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print "Solving problem", self.layoutName + print self.layoutText + + length = len(self.solution(searchAgents)) + print "Problem solved" + + handle.write('solution_length: "%s"\n' % length) + handle.close() + return True + + + + +class CornerHeuristicSanity(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicSanity, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + h0 = searchAgents.cornersHeuristic(start_state, problem) + succs = problem.getSuccessors(start_state) + # cornerConsistencyA + for succ in succs: + h1 = searchAgents.cornersHeuristic(succ[0], problem) + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + heuristic_cost = searchAgents.cornersHeuristic(start_state, problem) + true_cost = float(solutionDict['cost']) + # cornerNontrivial + if heuristic_cost == 0: + grades.addMessage('FAIL: must use non-trivial heuristic') + return False + # cornerAdmissible + if heuristic_cost > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = solutionDict['path'].split() + states = followPath(path, problem) + heuristics = [] + for state in states: + heuristics.append(searchAgents.cornersHeuristic(state, problem)) + for i in range(0, len(heuristics) - 1): + h0 = heuristics[i] + h1 = heuristics[i+1] + # cornerConsistencyB + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + # cornerPosH + if h0 < 0 or h1 <0: + grades.addMessage('FAIL: non-positive heuristic') + return False + # cornerGoalH + if heuristics[len(heuristics) - 1] != 0: + grades.addMessage('FAIL: heuristic non-zero at goal') + return False + grades.addMessage('PASS: heuristic value less than true cost at start state') + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# In order for a heuristic to be admissible, the value\n') + handle.write('# of the heuristic must be less at each state than the\n') + handle.write('# true cost of the optimal path from that state to a goal.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.close() + return True + + + +class CornerHeuristicPacman(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicPacman, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + total = 0 + true_cost = float(solutionDict['cost']) + thresholds = map(int, solutionDict['thresholds'].split()) + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + if searchAgents.cornersHeuristic(start_state, problem) > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = search.astar(problem, searchAgents.cornersHeuristic) + print "path:", path + print "path length:", len(path) + cost = problem.getCostOfActions(path) + if cost > true_cost: + grades.addMessage('FAIL: Inconsistent heuristic') + return False + expanded = problem._expanded + points = 0 + for threshold in thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(thresholds): + grades.addMessage('PASS: Heuristic resulted in expansion of %d nodes' % expanded) + else: + grades.addMessage('FAIL: Heuristic resulted in expansion of %d nodes' % expanded) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# This solution file specifies the length of the optimal path\n') + handle.write('# as well as the thresholds on number of nodes expanded to be\n') + handle.write('# used in scoring.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('thresholds: "2000 1600 1200"\n') + handle.close() + return True + diff --git a/search/searchTestClasses.pyc b/search/searchTestClasses.pyc new file mode 100644 index 0000000000000000000000000000000000000000..e7e9d362fd97498333c96d700b451212f532a769 GIT binary patch literal 26345 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 lCkC*AG*lifkL{{!Nw9SQ&d literal 0 HcmV?d00001 diff --git a/search/submission_autograder.py b/search/submission_autograder.py new file mode 100644 index 0000000..a521913 --- /dev/null +++ b/search/submission_autograder.py @@ -0,0 +1,41 @@ +# submission_autograder.py +# ------------------------ +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function +from codecs import open + +""" +CS 188 Local Submission Autograder +Written by the CS 188 Staff + +============================================================================== + _____ _ _ + / ____| | | | + | (___ | |_ ___ _ __ | | + \___ \| __/ _ \| '_ \| | + ____) | || (_) | |_) |_| + |_____/ \__\___/| .__/(_) + | | + |_| + +Modifying or tampering with this file is a violation of course policy. +If you're having trouble running the autograder, please contact the staff. +============================================================================== +""" +import bz2, base64 +exec(bz2.decompress(base64.b64decode('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'))) + diff --git a/search/testClasses.py b/search/testClasses.py new file mode 100644 index 0000000..6f95533 --- /dev/null +++ b/search/testClasses.py @@ -0,0 +1,206 @@ +# testClasses.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# import modules from python standard library +import inspect +import re +import sys + + +# Class which models a question in a project. Note that questions have a +# maximum number of points they are worth, and are composed of a series of +# test cases +class Question(object): + + def raiseNotDefined(self): + print 'Method not implemented: %s' % inspect.stack()[1][3] + sys.exit(1) + + def __init__(self, questionDict, display): + self.maxPoints = int(questionDict['max_points']) + self.testCases = [] + self.display = display + + def getDisplay(self): + return self.display + + def getMaxPoints(self): + return self.maxPoints + + # Note that 'thunk' must be a function which accepts a single argument, + # namely a 'grading' object + def addTestCase(self, testCase, thunk): + self.testCases.append((testCase, thunk)) + + def execute(self, grades): + self.raiseNotDefined() + +# Question in which all test cases must be passed in order to receive credit +class PassAllTestsQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + +class ExtraCreditPassAllTestsQuestion(Question): + def __init__(self, questionDict, display): + Question.__init__(self, questionDict, display) + self.extraPoints = int(questionDict['extra_points']) + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + grades.addPoints(self.extraPoints) + +# Question in which predict credit is given for test cases with a ``points'' property. +# All other tests are mandatory and must be passed. +class HackedPartialCreditQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + grades.assignZeroCredit() + + points = 0 + passed = True + for testCase, f in self.testCases: + testResult = f(grades) + if "points" in testCase.testDict: + if testResult: points += float(testCase.testDict["points"]) + else: + passed = passed and testResult + + ## FIXME: Below terrible hack to match q3's logic + if int(points) == self.maxPoints and not passed: + grades.assignZeroCredit() + else: + grades.addPoints(int(points)) + + +class Q6PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + results = [] + for _, f in self.testCases: + results.append(f(grades)) + if False in results: + grades.assignZeroCredit() + +class PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + for _, f in self.testCases: + if not f(grades): + grades.assignZeroCredit() + grades.fail("Tests failed.") + return False + + + +class NumberPassedQuestion(Question): + """Grade is the number of test cases passed.""" + + def execute(self, grades): + grades.addPoints([f(grades) for _, f in self.testCases].count(True)) + + + + + +# Template modeling a generic test case +class TestCase(object): + + def raiseNotDefined(self): + print 'Method not implemented: %s' % inspect.stack()[1][3] + sys.exit(1) + + def getPath(self): + return self.path + + def __init__(self, question, testDict): + self.question = question + self.testDict = testDict + self.path = testDict['path'] + self.messages = [] + + def __str__(self): + self.raiseNotDefined() + + def execute(self, grades, moduleDict, solutionDict): + self.raiseNotDefined() + + def writeSolution(self, moduleDict, filePath): + self.raiseNotDefined() + return True + + # Tests should call the following messages for grading + # to ensure a uniform format for test output. + # + # TODO: this is hairy, but we need to fix grading.py's interface + # to get a nice hierarchical project - question - test structure, + # then these should be moved into Question proper. + def testPass(self, grades): + grades.addMessage('PASS: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return True + + def testFail(self, grades): + grades.addMessage('FAIL: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return False + + # This should really be question level? + # + def testPartial(self, grades, points, maxPoints): + grades.addPoints(points) + extraCredit = max(0, points - maxPoints) + regularCredit = points - extraCredit + + grades.addMessage('%s: %s (%s of %s points)' % ("PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints)) + if extraCredit > 0: + grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,)) + + for line in self.messages: + grades.addMessage(' %s' % (line,)) + + return True + + def addMessage(self, message): + self.messages.extend(message.split('\n')) + diff --git a/search/testClasses.pyc b/search/testClasses.pyc new file mode 100644 index 0000000000000000000000000000000000000000..052bf10d6d78c6ba49f49dd826584f6a6e002f7f GIT binary patch literal 8736 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 BN#Fng literal 0 HcmV?d00001 diff --git a/search/test_cases/CONFIG b/search/test_cases/CONFIG new file mode 100644 index 0000000..dbed66b --- /dev/null +++ b/search/test_cases/CONFIG @@ -0,0 +1 @@ +order: "q1 q2 q3 q4 q5 q6 q7 q8" \ No newline at end of file diff --git a/search/test_cases/q1/CONFIG b/search/test_cases/q1/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/search/test_cases/q1/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/search/test_cases/q1/graph_backtrack.solution b/search/test_cases/q1/graph_backtrack.solution new file mode 100644 index 0000000..c52850c --- /dev/null +++ b/search/test_cases/q1/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A D C" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q1/graph_backtrack.test b/search/test_cases/q1/graph_backtrack.test new file mode 100644 index 0000000..05640a0 --- /dev/null +++ b/search/test_cases/q1/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/search/test_cases/q1/graph_bfs_vs_dfs.solution b/search/test_cases/q1/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..0680f92 --- /dev/null +++ b/search/test_cases/q1/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->D 0:D->G" +expanded_states: "A D" +rev_solution: "0:A->B 0:B->D 0:D->G" +rev_expanded_states: "A B D" diff --git a/search/test_cases/q1/graph_bfs_vs_dfs.test b/search/test_cases/q1/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..155e1fe --- /dev/null +++ b/search/test_cases/q1/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/search/test_cases/q1/graph_infinite.solution b/search/test_cases/q1/graph_infinite.solution new file mode 100644 index 0000000..82203ee --- /dev/null +++ b/search/test_cases/q1/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q1/graph_infinite.test b/search/test_cases/q1/graph_infinite.test new file mode 100644 index 0000000..692ac05 --- /dev/null +++ b/search/test_cases/q1/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/search/test_cases/q1/graph_manypaths.solution b/search/test_cases/q1/graph_manypaths.solution new file mode 100644 index 0000000..34b5a82 --- /dev/null +++ b/search/test_cases/q1/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->B2 0:B2->C 0:C->D 2:D->E2 0:E2->F 0:F->G" +expanded_states: "A B2 C D E2 F" +rev_solution: "0:A->B1 0:B1->C 0:C->D 0:D->E1 0:E1->F 0:F->G" +rev_expanded_states: "A B1 C D E1 F" diff --git a/search/test_cases/q1/graph_manypaths.test b/search/test_cases/q1/graph_manypaths.test new file mode 100644 index 0000000..953c4eb --- /dev/null +++ b/search/test_cases/q1/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/search/test_cases/q1/pacman_1.solution b/search/test_cases/q1/pacman_1.solution new file mode 100644 index 0000000..82a670c --- /dev/null +++ b/search/test_cases/q1/pacman_1.solution @@ -0,0 +1,40 @@ +# This is the solution file for test_cases/q1/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East East East East East East East South South South +East East East East East East East South South South South South South +South West West West West West West West West West West West West West +West West West West South West West West West West West West West West +""" +expanded_nodes: "146" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South South East North East East East South +South South West West West West West West West North North North North +North North North North West West West West West West West North North +North East East East East South East East East North North North West +West North North West West West West West West West West West West +West West West West West West West West West West West West West West +South South South South South South South South South East East East +North North North North North North North East East South South South +South South South East East North North North North North North East +East South South South South East East North North North North East +East East East East South South West West West South South East East +East South South West West West West West West South South West West +West West West South West West West West West South South East East +East East East East East North East East East East East North North +East East East East East East North East East East East East South +South West West West South West West West West West West South South +West West West West West South West West West West West West West West +West +""" +rev_expanded_nodes: "269" diff --git a/search/test_cases/q1/pacman_1.test b/search/test_cases/q1/pacman_1.test new file mode 100644 index 0000000..6ae5412 --- /dev/null +++ b/search/test_cases/q1/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic depth first search test +class: "PacmanSearchTest" +algorithm: "depthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/search/test_cases/q2/CONFIG b/search/test_cases/q2/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/search/test_cases/q2/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/search/test_cases/q2/graph_backtrack.solution b/search/test_cases/q2/graph_backtrack.solution new file mode 100644 index 0000000..6c669c2 --- /dev/null +++ b/search/test_cases/q2/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A D C B" diff --git a/search/test_cases/q2/graph_backtrack.test b/search/test_cases/q2/graph_backtrack.test new file mode 100644 index 0000000..2b35d8b --- /dev/null +++ b/search/test_cases/q2/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/search/test_cases/q2/graph_bfs_vs_dfs.solution b/search/test_cases/q2/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..05eecc8 --- /dev/null +++ b/search/test_cases/q2/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A D" diff --git a/search/test_cases/q2/graph_bfs_vs_dfs.test b/search/test_cases/q2/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..47b78a6 --- /dev/null +++ b/search/test_cases/q2/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/search/test_cases/q2/graph_infinite.solution b/search/test_cases/q2/graph_infinite.solution new file mode 100644 index 0000000..17b621c --- /dev/null +++ b/search/test_cases/q2/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q2/graph_infinite.test b/search/test_cases/q2/graph_infinite.test new file mode 100644 index 0000000..2cae9ad --- /dev/null +++ b/search/test_cases/q2/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/search/test_cases/q2/graph_manypaths.solution b/search/test_cases/q2/graph_manypaths.solution new file mode 100644 index 0000000..0cea422 --- /dev/null +++ b/search/test_cases/q2/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B2 C B1 D E2 F E1" diff --git a/search/test_cases/q2/graph_manypaths.test b/search/test_cases/q2/graph_manypaths.test new file mode 100644 index 0000000..7c636ea --- /dev/null +++ b/search/test_cases/q2/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/search/test_cases/q2/pacman_1.solution b/search/test_cases/q2/pacman_1.solution new file mode 100644 index 0000000..8f6d2bd --- /dev/null +++ b/search/test_cases/q2/pacman_1.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q2/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/search/test_cases/q2/pacman_1.test b/search/test_cases/q2/pacman_1.test new file mode 100644 index 0000000..c913f0c --- /dev/null +++ b/search/test_cases/q2/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic breadth first search test +class: "PacmanSearchTest" +algorithm: "breadthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/search/test_cases/q3/CONFIG b/search/test_cases/q3/CONFIG new file mode 100644 index 0000000..e5332c3 --- /dev/null +++ b/search/test_cases/q3/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" diff --git a/search/test_cases/q3/graph_backtrack.solution b/search/test_cases/q3/graph_backtrack.solution new file mode 100644 index 0000000..d150cb7 --- /dev/null +++ b/search/test_cases/q3/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/search/test_cases/q3/graph_backtrack.test b/search/test_cases/q3/graph_backtrack.test new file mode 100644 index 0000000..a74bd9e --- /dev/null +++ b/search/test_cases/q3/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/search/test_cases/q3/graph_bfs_vs_dfs.solution b/search/test_cases/q3/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..5dfffca --- /dev/null +++ b/search/test_cases/q3/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A B" diff --git a/search/test_cases/q3/graph_bfs_vs_dfs.test b/search/test_cases/q3/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..87aa465 --- /dev/null +++ b/search/test_cases/q3/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/search/test_cases/q3/graph_infinite.solution b/search/test_cases/q3/graph_infinite.solution new file mode 100644 index 0000000..c6cd195 --- /dev/null +++ b/search/test_cases/q3/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q3/graph_infinite.test b/search/test_cases/q3/graph_infinite.test new file mode 100644 index 0000000..80d807f --- /dev/null +++ b/search/test_cases/q3/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/search/test_cases/q3/graph_manypaths.solution b/search/test_cases/q3/graph_manypaths.solution new file mode 100644 index 0000000..628568f --- /dev/null +++ b/search/test_cases/q3/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/search/test_cases/q3/graph_manypaths.test b/search/test_cases/q3/graph_manypaths.test new file mode 100644 index 0000000..8c39dc7 --- /dev/null +++ b/search/test_cases/q3/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/search/test_cases/q3/ucs_0_graph.solution b/search/test_cases/q3/ucs_0_graph.solution new file mode 100644 index 0000000..b8c1509 --- /dev/null +++ b/search/test_cases/q3/ucs_0_graph.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_0_graph.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/search/test_cases/q3/ucs_0_graph.test b/search/test_cases/q3/ucs_0_graph.test new file mode 100644 index 0000000..e8f3d4c --- /dev/null +++ b/search/test_cases/q3/ucs_0_graph.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + |1 + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/search/test_cases/q3/ucs_1_problemC.solution b/search/test_cases/q3/ucs_1_problemC.solution new file mode 100644 index 0000000..dc8fc4c --- /dev/null +++ b/search/test_cases/q3/ucs_1_problemC.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_1_problemC.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/search/test_cases/q3/ucs_1_problemC.test b/search/test_cases/q3/ucs_1_problemC.test new file mode 100644 index 0000000..1ce714d --- /dev/null +++ b/search/test_cases/q3/ucs_1_problemC.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +#costFn: "lambda pos: 1" diff --git a/search/test_cases/q3/ucs_2_problemE.solution b/search/test_cases/q3/ucs_2_problemE.solution new file mode 100644 index 0000000..d84245f --- /dev/null +++ b/search/test_cases/q3/ucs_2_problemE.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_2_problemE.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +expanded_nodes: "260" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +rev_expanded_nodes: "260" diff --git a/search/test_cases/q3/ucs_2_problemE.test b/search/test_cases/q3/ucs_2_problemE.test new file mode 100644 index 0000000..3c609f2 --- /dev/null +++ b/search/test_cases/q3/ucs_2_problemE.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: .5 ** pos[0]" diff --git a/search/test_cases/q3/ucs_3_problemW.solution b/search/test_cases/q3/ucs_3_problemW.solution new file mode 100644 index 0000000..e04325d --- /dev/null +++ b/search/test_cases/q3/ucs_3_problemW.solution @@ -0,0 +1,34 @@ +# This is the solution file for test_cases/q3/ucs_3_problemW.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +expanded_nodes: "173" +rev_solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +rev_expanded_nodes: "173" diff --git a/search/test_cases/q3/ucs_3_problemW.test b/search/test_cases/q3/ucs_3_problemW.test new file mode 100644 index 0000000..fbc2fad --- /dev/null +++ b/search/test_cases/q3/ucs_3_problemW.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: 2 ** pos[0]" diff --git a/search/test_cases/q3/ucs_4_testSearch.solution b/search/test_cases/q3/ucs_4_testSearch.solution new file mode 100644 index 0000000..b8c5303 --- /dev/null +++ b/search/test_cases/q3/ucs_4_testSearch.solution @@ -0,0 +1,12 @@ +# This is the solution file for test_cases/q3/ucs_4_testSearch.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 2.0 of the numbers below. +solution: """ +West East East South South West West +""" +expanded_nodes: "14" +rev_solution: """ +West East East South South West West +""" +rev_expanded_nodes: "13" diff --git a/search/test_cases/q3/ucs_4_testSearch.test b/search/test_cases/q3/ucs_4_testSearch.test new file mode 100644 index 0000000..a16c6d8 --- /dev/null +++ b/search/test_cases/q3/ucs_4_testSearch.test @@ -0,0 +1,16 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "testSearch" +layout: """ +%%%%% +%.P % +%%% % +%. % +%%%%% +""" +searchProblemClass: "FoodSearchProblem" +leewayFactor: "2" + diff --git a/search/test_cases/q3/ucs_5_goalAtDequeue.solution b/search/test_cases/q3/ucs_5_goalAtDequeue.solution new file mode 100644 index 0000000..7d6c982 --- /dev/null +++ b/search/test_cases/q3/ucs_5_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_5_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q3/ucs_5_goalAtDequeue.test b/search/test_cases/q3/ucs_5_goalAtDequeue.test new file mode 100644 index 0000000..72b35bc --- /dev/null +++ b/search/test_cases/q3/ucs_5_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/search/test_cases/q4/CONFIG b/search/test_cases/q4/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/search/test_cases/q4/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/search/test_cases/q4/astar_0.solution b/search/test_cases/q4/astar_0.solution new file mode 100644 index 0000000..459cadd --- /dev/null +++ b/search/test_cases/q4/astar_0.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_0.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/search/test_cases/q4/astar_0.test b/search/test_cases/q4/astar_0.test new file mode 100644 index 0000000..9b3b539 --- /dev/null +++ b/search/test_cases/q4/astar_0.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + | + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/search/test_cases/q4/astar_1_graph_heuristic.solution b/search/test_cases/q4/astar_1_graph_heuristic.solution new file mode 100644 index 0000000..7767c27 --- /dev/null +++ b/search/test_cases/q4/astar_1_graph_heuristic.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_1_graph_heuristic.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0 0 2" +expanded_states: "S A D C" +rev_solution: "0 0 2" +rev_expanded_states: "S A D C" diff --git a/search/test_cases/q4/astar_1_graph_heuristic.test b/search/test_cases/q4/astar_1_graph_heuristic.test new file mode 100644 index 0000000..b5afd79 --- /dev/null +++ b/search/test_cases/q4/astar_1_graph_heuristic.test @@ -0,0 +1,54 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 2 3 2 + S --- A --- C ---> G + | \ / ^ +3 | \ 5 / 1 / + | \ / / + B --- D -------/ + 4 5 + +S is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +The heuristic value of each state is: + S 6.0 + A 2.5 + B 5.25 + C 1.125 + D 1.0625 + G 0 +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: S +goal_states: G +S 0 A 2.0 +S 1 B 3.0 +S 2 D 5.0 +A 0 C 3.0 +A 1 S 2.0 +B 0 D 4.0 +B 1 S 3.0 +C 0 A 3.0 +C 1 D 1.0 +C 2 G 2.0 +D 0 B 4.0 +D 1 C 1.0 +D 2 G 5.0 +D 3 S 5.0 +""" +heuristic: """ +S 6.0 +A 2.5 +B 5.25 +C 1.125 +D 1.0625 +G 0 +""" diff --git a/search/test_cases/q4/astar_2_manhattan.solution b/search/test_cases/q4/astar_2_manhattan.solution new file mode 100644 index 0000000..65bf5f5 --- /dev/null +++ b/search/test_cases/q4/astar_2_manhattan.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q4/astar_2_manhattan.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "221" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "221" diff --git a/search/test_cases/q4/astar_2_manhattan.test b/search/test_cases/q4/astar_2_manhattan.test new file mode 100644 index 0000000..e936195 --- /dev/null +++ b/search/test_cases/q4/astar_2_manhattan.test @@ -0,0 +1,27 @@ +class: "PacmanSearchTest" +algorithm: "aStarSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +heuristic: "manhattanHeuristic" diff --git a/search/test_cases/q4/astar_3_goalAtDequeue.solution b/search/test_cases/q4/astar_3_goalAtDequeue.solution new file mode 100644 index 0000000..edb3502 --- /dev/null +++ b/search/test_cases/q4/astar_3_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_3_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/search/test_cases/q4/astar_3_goalAtDequeue.test b/search/test_cases/q4/astar_3_goalAtDequeue.test new file mode 100644 index 0000000..c4d1903 --- /dev/null +++ b/search/test_cases/q4/astar_3_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/search/test_cases/q4/graph_backtrack.solution b/search/test_cases/q4/graph_backtrack.solution new file mode 100644 index 0000000..fc51794 --- /dev/null +++ b/search/test_cases/q4/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/search/test_cases/q4/graph_backtrack.test b/search/test_cases/q4/graph_backtrack.test new file mode 100644 index 0000000..84e0126 --- /dev/null +++ b/search/test_cases/q4/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/search/test_cases/q4/graph_manypaths.solution b/search/test_cases/q4/graph_manypaths.solution new file mode 100644 index 0000000..0caa767 --- /dev/null +++ b/search/test_cases/q4/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/search/test_cases/q4/graph_manypaths.test b/search/test_cases/q4/graph_manypaths.test new file mode 100644 index 0000000..82fdf87 --- /dev/null +++ b/search/test_cases/q4/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/search/test_cases/q5/CONFIG b/search/test_cases/q5/CONFIG new file mode 100644 index 0000000..e7c6582 --- /dev/null +++ b/search/test_cases/q5/CONFIG @@ -0,0 +1,3 @@ +class: "PassAllTestsQuestion" +max_points: "3" +depends: "q2" \ No newline at end of file diff --git a/search/test_cases/q5/corner_tiny_corner.solution b/search/test_cases/q5/corner_tiny_corner.solution new file mode 100644 index 0000000..161bf15 --- /dev/null +++ b/search/test_cases/q5/corner_tiny_corner.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q5/corner_tiny_corner.test. +solution_length: "28" diff --git a/search/test_cases/q5/corner_tiny_corner.test b/search/test_cases/q5/corner_tiny_corner.test new file mode 100644 index 0000000..823bd47 --- /dev/null +++ b/search/test_cases/q5/corner_tiny_corner.test @@ -0,0 +1,14 @@ +class: "CornerProblemTest" + +layoutName: "tinyCorner" +layout: """ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% +""" + diff --git a/search/test_cases/q6/CONFIG b/search/test_cases/q6/CONFIG new file mode 100644 index 0000000..b76e0eb --- /dev/null +++ b/search/test_cases/q6/CONFIG @@ -0,0 +1,3 @@ +class: "Q6PartialCreditQuestion" +max_points: "3" +depends: "q4" \ No newline at end of file diff --git a/search/test_cases/q6/corner_sanity_1.solution b/search/test_cases/q6/corner_sanity_1.solution new file mode 100644 index 0000000..4385d9b --- /dev/null +++ b/search/test_cases/q6/corner_sanity_1.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +North South South East East East North North +""" diff --git a/search/test_cases/q6/corner_sanity_1.test b/search/test_cases/q6/corner_sanity_1.test new file mode 100644 index 0000000..93379ac --- /dev/null +++ b/search/test_cases/q6/corner_sanity_1.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +%P % +%. .% +%%%%%% +""" + diff --git a/search/test_cases/q6/corner_sanity_2.solution b/search/test_cases/q6/corner_sanity_2.solution new file mode 100644 index 0000000..1aebe8a --- /dev/null +++ b/search/test_cases/q6/corner_sanity_2.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +West North North East East East South South +""" diff --git a/search/test_cases/q6/corner_sanity_2.test b/search/test_cases/q6/corner_sanity_2.test new file mode 100644 index 0000000..18184a8 --- /dev/null +++ b/search/test_cases/q6/corner_sanity_2.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +% %% % +%.P%.% +%%%%%% +""" + diff --git a/search/test_cases/q6/corner_sanity_3.solution b/search/test_cases/q6/corner_sanity_3.solution new file mode 100644 index 0000000..c02dd57 --- /dev/null +++ b/search/test_cases/q6/corner_sanity_3.solution @@ -0,0 +1,9 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "28" +path: """ +South South South West West West West East East East East East North +North North North North West West West South South South West West +North North North +""" diff --git a/search/test_cases/q6/corner_sanity_3.test b/search/test_cases/q6/corner_sanity_3.test new file mode 100644 index 0000000..8f30442 --- /dev/null +++ b/search/test_cases/q6/corner_sanity_3.test @@ -0,0 +1,15 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%%%% +%.% .% +% % % % +% % %P % +% % % +%%%%% % +%. .% +%%%%%%%% +""" + diff --git a/search/test_cases/q6/medium_corners.solution b/search/test_cases/q6/medium_corners.solution new file mode 100644 index 0000000..913dc45 --- /dev/null +++ b/search/test_cases/q6/medium_corners.solution @@ -0,0 +1,16 @@ +# This solution file specifies the length of the optimal path +# as well as the thresholds on number of nodes expanded to be +# used in scoring. +cost: "106" +path: """ +North East East East East North North West West West West North North +North North North North North North West West West West South South +East East East East South South South South South South West West +South South South West West East East North North North East East East +East East East East East South South East East East East East North +North East East North North East East North North East East East East +South South South South East East North North East East South South +South South South North North North North North North North West West +North North East East North North +""" +thresholds: "2000 1600 1200" diff --git a/search/test_cases/q6/medium_corners.test b/search/test_cases/q6/medium_corners.test new file mode 100644 index 0000000..dfa0a68 --- /dev/null +++ b/search/test_cases/q6/medium_corners.test @@ -0,0 +1,19 @@ +class: "CornerHeuristicPacman" + +# The following specifies the layout to be used +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" diff --git a/search/test_cases/q7/CONFIG b/search/test_cases/q7/CONFIG new file mode 100644 index 0000000..ee85183 --- /dev/null +++ b/search/test_cases/q7/CONFIG @@ -0,0 +1,3 @@ +class: "PartialCreditQuestion" +max_points: "4" +depends: "q4" \ No newline at end of file diff --git a/search/test_cases/q7/food_heuristic_1.solution b/search/test_cases/q7/food_heuristic_1.solution new file mode 100644 index 0000000..7a287f8 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_1.test. +solution_cost: "0" diff --git a/search/test_cases/q7/food_heuristic_1.test b/search/test_cases/q7/food_heuristic_1.test new file mode 100644 index 0000000..7545a7a --- /dev/null +++ b/search/test_cases/q7/food_heuristic_1.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 1" +layout: """ +%%%%%% +% % +% % +%P % +%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_10.solution b/search/test_cases/q7/food_heuristic_10.solution new file mode 100644 index 0000000..1917f05 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_10.test. +solution_cost: "7" diff --git a/search/test_cases/q7/food_heuristic_10.test b/search/test_cases/q7/food_heuristic_10.test new file mode 100644 index 0000000..212c7bd --- /dev/null +++ b/search/test_cases/q7/food_heuristic_10.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 10" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_11.solution b/search/test_cases/q7/food_heuristic_11.solution new file mode 100644 index 0000000..11c3289 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_11.test. +solution_cost: "8" diff --git a/search/test_cases/q7/food_heuristic_11.test b/search/test_cases/q7/food_heuristic_11.test new file mode 100644 index 0000000..f5e6ed4 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_11.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 11" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_12.solution b/search/test_cases/q7/food_heuristic_12.solution new file mode 100644 index 0000000..0edcc02 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_12.test. +solution_cost: "1" diff --git a/search/test_cases/q7/food_heuristic_12.test b/search/test_cases/q7/food_heuristic_12.test new file mode 100644 index 0000000..cc99a25 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_12.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 12" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_13.solution b/search/test_cases/q7/food_heuristic_13.solution new file mode 100644 index 0000000..c25d50b --- /dev/null +++ b/search/test_cases/q7/food_heuristic_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_13.test. +solution_cost: "5" diff --git a/search/test_cases/q7/food_heuristic_13.test b/search/test_cases/q7/food_heuristic_13.test new file mode 100644 index 0000000..09d6f1e --- /dev/null +++ b/search/test_cases/q7/food_heuristic_13.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 13" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_14.solution b/search/test_cases/q7/food_heuristic_14.solution new file mode 100644 index 0000000..e6cc475 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_14.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_14.test. +solution_cost: "31" diff --git a/search/test_cases/q7/food_heuristic_14.test b/search/test_cases/q7/food_heuristic_14.test new file mode 100644 index 0000000..58982e3 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_14.test @@ -0,0 +1,19 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 14" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_15.solution b/search/test_cases/q7/food_heuristic_15.solution new file mode 100644 index 0000000..4eca0f1 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_15.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_15.test. +solution_cost: "21" diff --git a/search/test_cases/q7/food_heuristic_15.test b/search/test_cases/q7/food_heuristic_15.test new file mode 100644 index 0000000..df605c1 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_15.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 15" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_16.solution b/search/test_cases/q7/food_heuristic_16.solution new file mode 100644 index 0000000..8d89992 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_16.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_16.test. +solution_cost: "7" diff --git a/search/test_cases/q7/food_heuristic_16.test b/search/test_cases/q7/food_heuristic_16.test new file mode 100644 index 0000000..762b433 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_16.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 16" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_17.solution b/search/test_cases/q7/food_heuristic_17.solution new file mode 100644 index 0000000..63a9a1b --- /dev/null +++ b/search/test_cases/q7/food_heuristic_17.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_17.test. +solution_cost: "16" diff --git a/search/test_cases/q7/food_heuristic_17.test b/search/test_cases/q7/food_heuristic_17.test new file mode 100644 index 0000000..a923f67 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_17.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 17" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_2.solution b/search/test_cases/q7/food_heuristic_2.solution new file mode 100644 index 0000000..ca5aba1 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_2.test. +solution_cost: "0" diff --git a/search/test_cases/q7/food_heuristic_2.test b/search/test_cases/q7/food_heuristic_2.test new file mode 100644 index 0000000..956e75d --- /dev/null +++ b/search/test_cases/q7/food_heuristic_2.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 2" +layout: """ +%%% +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +%P% +% % +% % +% % +% % +% % +%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_3.solution b/search/test_cases/q7/food_heuristic_3.solution new file mode 100644 index 0000000..d1694b5 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_3.test. +solution_cost: "0" diff --git a/search/test_cases/q7/food_heuristic_3.test b/search/test_cases/q7/food_heuristic_3.test new file mode 100644 index 0000000..250a8b1 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_3.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 3" +layout: """ +%%%% +% % +% % +%P % +% % +% % +%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_4.solution b/search/test_cases/q7/food_heuristic_4.solution new file mode 100644 index 0000000..6e1e82a --- /dev/null +++ b/search/test_cases/q7/food_heuristic_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_4.test. +solution_cost: "0" diff --git a/search/test_cases/q7/food_heuristic_4.test b/search/test_cases/q7/food_heuristic_4.test new file mode 100644 index 0000000..ed86a0c --- /dev/null +++ b/search/test_cases/q7/food_heuristic_4.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 4" +layout: """ +%%%%%%%% +% % % +% % %% % +% %P%% % +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_5.solution b/search/test_cases/q7/food_heuristic_5.solution new file mode 100644 index 0000000..779e9e6 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_5.test. +solution_cost: "11" diff --git a/search/test_cases/q7/food_heuristic_5.test b/search/test_cases/q7/food_heuristic_5.test new file mode 100644 index 0000000..1f44c48 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_5.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 5" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_6.solution b/search/test_cases/q7/food_heuristic_6.solution new file mode 100644 index 0000000..906b510 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_6.test. +solution_cost: "5" diff --git a/search/test_cases/q7/food_heuristic_6.test b/search/test_cases/q7/food_heuristic_6.test new file mode 100644 index 0000000..01d7f32 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_6.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 6" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_7.solution b/search/test_cases/q7/food_heuristic_7.solution new file mode 100644 index 0000000..5994a7b --- /dev/null +++ b/search/test_cases/q7/food_heuristic_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_7.test. +solution_cost: "7" diff --git a/search/test_cases/q7/food_heuristic_7.test b/search/test_cases/q7/food_heuristic_7.test new file mode 100644 index 0000000..b1db372 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_7.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 7" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_8.solution b/search/test_cases/q7/food_heuristic_8.solution new file mode 100644 index 0000000..0e4fb08 --- /dev/null +++ b/search/test_cases/q7/food_heuristic_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_8.test. +solution_cost: "5" diff --git a/search/test_cases/q7/food_heuristic_8.test b/search/test_cases/q7/food_heuristic_8.test new file mode 100644 index 0000000..b9430af --- /dev/null +++ b/search/test_cases/q7/food_heuristic_8.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 8" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_9.solution b/search/test_cases/q7/food_heuristic_9.solution new file mode 100644 index 0000000..1470d9a --- /dev/null +++ b/search/test_cases/q7/food_heuristic_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_9.test. +solution_cost: "6" diff --git a/search/test_cases/q7/food_heuristic_9.test b/search/test_cases/q7/food_heuristic_9.test new file mode 100644 index 0000000..799b41d --- /dev/null +++ b/search/test_cases/q7/food_heuristic_9.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 9" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/search/test_cases/q7/food_heuristic_grade_tricky.solution b/search/test_cases/q7/food_heuristic_grade_tricky.solution new file mode 100644 index 0000000..cd6fd7d --- /dev/null +++ b/search/test_cases/q7/food_heuristic_grade_tricky.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_grade_tricky.test. +# File intentionally blank. diff --git a/search/test_cases/q7/food_heuristic_grade_tricky.test b/search/test_cases/q7/food_heuristic_grade_tricky.test new file mode 100644 index 0000000..081fb0d --- /dev/null +++ b/search/test_cases/q7/food_heuristic_grade_tricky.test @@ -0,0 +1,19 @@ +class: "HeuristicGrade" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "trickySearch" +layout: """ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% +""" +# One point always, an extra point for each +# threshold passed. +basePoints: "1" +gradingThresholds: "15000 12000 9000 7000" + diff --git a/search/test_cases/q8/CONFIG b/search/test_cases/q8/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/search/test_cases/q8/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/search/test_cases/q8/closest_dot_1.solution b/search/test_cases/q8/closest_dot_1.solution new file mode 100644 index 0000000..300fc25 --- /dev/null +++ b/search/test_cases/q8/closest_dot_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_1.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_1.test b/search/test_cases/q8/closest_dot_1.test new file mode 100644 index 0000000..672989f --- /dev/null +++ b/search/test_cases/q8/closest_dot_1.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 1" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_10.solution b/search/test_cases/q8/closest_dot_10.solution new file mode 100644 index 0000000..174b5dd --- /dev/null +++ b/search/test_cases/q8/closest_dot_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_10.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_10.test b/search/test_cases/q8/closest_dot_10.test new file mode 100644 index 0000000..b1e0f33 --- /dev/null +++ b/search/test_cases/q8/closest_dot_10.test @@ -0,0 +1,17 @@ +class: "ClosestDotTest" + +layoutName: "Test 10" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_11.solution b/search/test_cases/q8/closest_dot_11.solution new file mode 100644 index 0000000..80bbe38 --- /dev/null +++ b/search/test_cases/q8/closest_dot_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_11.test. +solution_length: "2" diff --git a/search/test_cases/q8/closest_dot_11.test b/search/test_cases/q8/closest_dot_11.test new file mode 100644 index 0000000..0310a1e --- /dev/null +++ b/search/test_cases/q8/closest_dot_11.test @@ -0,0 +1,30 @@ +class: "ClosestDotTest" + +layoutName: "Test 11" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/search/test_cases/q8/closest_dot_12.solution b/search/test_cases/q8/closest_dot_12.solution new file mode 100644 index 0000000..6f38bcb --- /dev/null +++ b/search/test_cases/q8/closest_dot_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_12.test. +solution_length: "3" diff --git a/search/test_cases/q8/closest_dot_12.test b/search/test_cases/q8/closest_dot_12.test new file mode 100644 index 0000000..a17b628 --- /dev/null +++ b/search/test_cases/q8/closest_dot_12.test @@ -0,0 +1,13 @@ +class: "ClosestDotTest" + +layoutName: "Test 12" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_13.solution b/search/test_cases/q8/closest_dot_13.solution new file mode 100644 index 0000000..7afa908 --- /dev/null +++ b/search/test_cases/q8/closest_dot_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_13.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_13.test b/search/test_cases/q8/closest_dot_13.test new file mode 100644 index 0000000..87c423d --- /dev/null +++ b/search/test_cases/q8/closest_dot_13.test @@ -0,0 +1,12 @@ +class: "ClosestDotTest" + +layoutName: "Test 13" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_2.solution b/search/test_cases/q8/closest_dot_2.solution new file mode 100644 index 0000000..16d75de --- /dev/null +++ b/search/test_cases/q8/closest_dot_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_2.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_2.test b/search/test_cases/q8/closest_dot_2.test new file mode 100644 index 0000000..4b59602 --- /dev/null +++ b/search/test_cases/q8/closest_dot_2.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 2" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_3.solution b/search/test_cases/q8/closest_dot_3.solution new file mode 100644 index 0000000..cbd5974 --- /dev/null +++ b/search/test_cases/q8/closest_dot_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_3.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_3.test b/search/test_cases/q8/closest_dot_3.test new file mode 100644 index 0000000..aa2a3af --- /dev/null +++ b/search/test_cases/q8/closest_dot_3.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 3" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_4.solution b/search/test_cases/q8/closest_dot_4.solution new file mode 100644 index 0000000..ca520b5 --- /dev/null +++ b/search/test_cases/q8/closest_dot_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_4.test. +solution_length: "3" diff --git a/search/test_cases/q8/closest_dot_4.test b/search/test_cases/q8/closest_dot_4.test new file mode 100644 index 0000000..8499f6d --- /dev/null +++ b/search/test_cases/q8/closest_dot_4.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 4" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_5.solution b/search/test_cases/q8/closest_dot_5.solution new file mode 100644 index 0000000..5c526a2 --- /dev/null +++ b/search/test_cases/q8/closest_dot_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_5.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_5.test b/search/test_cases/q8/closest_dot_5.test new file mode 100644 index 0000000..dfaee3d --- /dev/null +++ b/search/test_cases/q8/closest_dot_5.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 5" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_6.solution b/search/test_cases/q8/closest_dot_6.solution new file mode 100644 index 0000000..b06468a --- /dev/null +++ b/search/test_cases/q8/closest_dot_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_6.test. +solution_length: "2" diff --git a/search/test_cases/q8/closest_dot_6.test b/search/test_cases/q8/closest_dot_6.test new file mode 100644 index 0000000..bc50c57 --- /dev/null +++ b/search/test_cases/q8/closest_dot_6.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 6" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_7.solution b/search/test_cases/q8/closest_dot_7.solution new file mode 100644 index 0000000..3231b28 --- /dev/null +++ b/search/test_cases/q8/closest_dot_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_7.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_7.test b/search/test_cases/q8/closest_dot_7.test new file mode 100644 index 0000000..746e89a --- /dev/null +++ b/search/test_cases/q8/closest_dot_7.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 7" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_8.solution b/search/test_cases/q8/closest_dot_8.solution new file mode 100644 index 0000000..646e621 --- /dev/null +++ b/search/test_cases/q8/closest_dot_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_8.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_8.test b/search/test_cases/q8/closest_dot_8.test new file mode 100644 index 0000000..c266ae1 --- /dev/null +++ b/search/test_cases/q8/closest_dot_8.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 8" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/search/test_cases/q8/closest_dot_9.solution b/search/test_cases/q8/closest_dot_9.solution new file mode 100644 index 0000000..6c94aa5 --- /dev/null +++ b/search/test_cases/q8/closest_dot_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_9.test. +solution_length: "1" diff --git a/search/test_cases/q8/closest_dot_9.test b/search/test_cases/q8/closest_dot_9.test new file mode 100644 index 0000000..da078de --- /dev/null +++ b/search/test_cases/q8/closest_dot_9.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 9" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/search/textDisplay.py b/search/textDisplay.py new file mode 100644 index 0000000..e920ad4 --- /dev/null +++ b/search/textDisplay.py @@ -0,0 +1,81 @@ +# textDisplay.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import time +try: + import pacman +except: + pass + +DRAW_EVERY = 1 +SLEEP_TIME = 0 # This can be overwritten by __init__ +DISPLAY_MOVES = False +QUIET = False # Supresses output + +class NullGraphics: + def initialize(self, state, isBlue = False): + pass + + def update(self, state): + pass + + def checkNullDisplay(self): + return True + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print state + + def updateDistributions(self, dist): + pass + + def finish(self): + pass + +class PacmanGraphics: + def __init__(self, speed=None): + if speed != None: + global SLEEP_TIME + SLEEP_TIME = speed + + def initialize(self, state, isBlue = False): + self.draw(state) + self.pause() + self.turn = 0 + self.agentCounter = 0 + + def update(self, state): + numAgents = len(state.agentStates) + self.agentCounter = (self.agentCounter + 1) % numAgents + if self.agentCounter == 0: + self.turn += 1 + if DISPLAY_MOVES: + ghosts = [pacman.nearestPoint(state.getGhostPosition(i)) for i in range(1, numAgents)] + print "%4d) P: %-8s" % (self.turn, str(pacman.nearestPoint(state.getPacmanPosition()))),'| Score: %-5d' % state.score,'| Ghosts:', ghosts + if self.turn % DRAW_EVERY == 0: + self.draw(state) + self.pause() + if state._win or state._lose: + self.draw(state) + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print state + + def finish(self): + pass diff --git a/search/textDisplay.pyc b/search/textDisplay.pyc new file mode 100644 index 0000000000000000000000000000000000000000..09e3b1167d9d9536618a01183258be9a698cbb9b GIT binary patch literal 3800 zcmc&%U2hvj6uq-{oH)%FO-cC>RS;Adq#}^YhoXW=8xvAB!K_O|EyHT@Zjw!%wd)zH z6s1q?PeI}jfK&tzeW(Db5)wS{!~+s9eeYXe;GDbh2StUKS~#BByEE4_XYQOicapm| zJ-KxA)@DNvJ~=$!L9siiB7BWBMQS2f=X$S3YDPBGaWTfEMm~x>sd*CRB+N;im)e-t z1&Q(!O-M8*H$<+BT&#^tgw~=&yBiFu_HXq@D~jvOYe6#(J86<`*DZ|3PTj?yxs}AWo8i@$ z+gF{2DNS4wHj~fer#U!dA*R6x%fF(ggHI2i)<_UC0Nr+kSR8}ZL&rILr5{r_h$;It zoaZ};oFZg35`=RYxUmyAE|FRXv3(Y84`OAA6zgL!fOXf1^W?e_Gem|y4_vYrXFO=e zaa%X4dahiq1n1Y*%OhS9>DLasHhz9+8HCTmIO;NrF!qhCzBdFPO;V!Qh*`mt5_Q5W zFW}jW^FIKp(|{9j9m1yUh!X2Y!ijmzm9Z9taGm7Su1i{}eQ5|TG3O^W1I%p_I zf|6%zD@hZ(b7BBYrPEEIAqdiNFAf4FUl8oIqHdF~q{q8q)5bm{?DN&9()ov2QDprY zRA~hloZlpb|4}CQUKR zcH}XOZr=<}9yA)e#-$p$=4lLcrAPNYS;*GSP@v6|OP+M@(j~vE_k}mSAjjo)Zpt=B z0+|pYsJ$mEA0Lxz=qt$XxQ;&|`#Da5?*myMJ%rFw5+pBCCz)>*e3R!bWj3RdC#%_ z6L|NJqH-K&WvyB{w|p^JfB!==XBFE%BHjQ3(>7V5Px_J0J6F%3I ziP1>2k7kRrnCqeWTiNsfTriXX>qFprvE)+)g2JX^$CiUj{AlCFbXq| priority" + self.priorityFunction = priorityFunction # store the priority function + PriorityQueue.__init__(self) # super-class initializer + + def push(self, item): + "Adds an item to the queue with priority from the priority function" + PriorityQueue.push(self, item, self.priorityFunction(item)) + + +def manhattanDistance( xy1, xy2 ): + "Returns the Manhattan distance between points xy1 and xy2" + return abs( xy1[0] - xy2[0] ) + abs( xy1[1] - xy2[1] ) + +""" + Data structures and functions useful for various course projects + + The search project should not need anything below this line. +""" + +class Counter(dict): + """ + A counter keeps track of counts for a set of keys. + + The counter class is an extension of the standard python + dictionary type. It is specialized to have number values + (integers or floats), and includes a handful of additional + functions to ease the task of counting data. In particular, + all keys are defaulted to have value 0. Using a dictionary: + + a = {} + print a['test'] + + would give an error, while the Counter class analogue: + + >>> a = Counter() + >>> print a['test'] + 0 + + returns the default 0 value. Note that to reference a key + that you know is contained in the counter, + you can still use the dictionary syntax: + + >>> a = Counter() + >>> a['test'] = 2 + >>> print a['test'] + 2 + + This is very useful for counting things without initializing their counts, + see for example: + + >>> a['blah'] += 1 + >>> print a['blah'] + 1 + + The counter also includes additional functionality useful in implementing + the classifiers for this assignment. Two counters can be added, + subtracted or multiplied together. See below for details. They can + also be normalized and their total count and arg max can be extracted. + """ + def __getitem__(self, idx): + self.setdefault(idx, 0) + return dict.__getitem__(self, idx) + + def incrementAll(self, keys, count): + """ + Increments all elements of keys by the same count. + + >>> a = Counter() + >>> a.incrementAll(['one','two', 'three'], 1) + >>> a['one'] + 1 + >>> a['two'] + 1 + """ + for key in keys: + self[key] += count + + def argMax(self): + """ + Returns the key with the highest value. + """ + if len(self.keys()) == 0: return None + all = self.items() + values = [x[1] for x in all] + maxIndex = values.index(max(values)) + return all[maxIndex][0] + + def sortedKeys(self): + """ + Returns a list of keys sorted by their values. Keys + with the highest values will appear first. + + >>> a = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> a['third'] = 1 + >>> a.sortedKeys() + ['second', 'third', 'first'] + """ + sortedItems = self.items() + compare = lambda x, y: sign(y[1] - x[1]) + sortedItems.sort(cmp=compare) + return [x[0] for x in sortedItems] + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + + def normalize(self): + """ + Edits the counter such that the total count of all + keys sums to 1. The ratio of counts for all keys + will remain the same. Note that normalizing an empty + Counter will result in an error. + """ + total = float(self.totalCount()) + if total == 0: return + for key in self.keys(): + self[key] = self[key] / total + + def divideAll(self, divisor): + """ + Divides all counts by divisor + """ + divisor = float(divisor) + for key in self: + self[key] /= divisor + + def copy(self): + """ + Returns a copy of the counter + """ + return Counter(dict.copy(self)) + + def __mul__(self, y ): + """ + Multiplying two counters gives the dot product of their vectors where + each unique label is a vector element. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['second'] = 5 + >>> a['third'] = 1.5 + >>> a['fourth'] = 2.5 + >>> a * b + 14 + """ + sum = 0 + x = self + if len(x) > len(y): + x,y = y,x + for key in x: + if key not in y: + continue + sum += x[key] * y[key] + return sum + + def __radd__(self, y): + """ + Adding another counter to a counter increments the current counter + by the values stored in the second counter. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> a += b + >>> a['first'] + 1 + """ + for key, value in y.items(): + self[key] += value + + def __add__( self, y ): + """ + Adding two counters gives a counter with the union of all keys and + counts of the second added to counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a + b)['first'] + 1 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] + y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = y[key] + return addend + + def __sub__( self, y ): + """ + Subtracting a counter from another gives a counter with the union of all keys and + counts of the second subtracted from counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a - b)['first'] + -5 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] - y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = -1 * y[key] + return addend + +def raiseNotDefined(): + fileName = inspect.stack()[1][1] + line = inspect.stack()[1][2] + method = inspect.stack()[1][3] + + print "*** Method not implemented: %s at line %s of %s" % (method, line, fileName) + sys.exit(1) + +def normalize(vectorOrCounter): + """ + normalize a vector or counter by dividing each value by the sum of all values + """ + normalizedCounter = Counter() + if type(vectorOrCounter) == type(normalizedCounter): + counter = vectorOrCounter + total = float(counter.totalCount()) + if total == 0: return counter + for key in counter.keys(): + value = counter[key] + normalizedCounter[key] = value / total + return normalizedCounter + else: + vector = vectorOrCounter + s = float(sum(vector)) + if s == 0: return vector + return [el / s for el in vector] + +def nSample(distribution, values, n): + if sum(distribution) != 1: + distribution = normalize(distribution) + rand = [random.random() for i in range(n)] + rand.sort() + samples = [] + samplePos, distPos, cdf = 0,0, distribution[0] + while samplePos < n: + if rand[samplePos] < cdf: + samplePos += 1 + samples.append(values[distPos]) + else: + distPos += 1 + cdf += distribution[distPos] + return samples + +def sample(distribution, values = None): + if type(distribution) == Counter: + items = sorted(distribution.items()) + distribution = [i[1] for i in items] + values = [i[0] for i in items] + if sum(distribution) != 1: + distribution = normalize(distribution) + choice = random.random() + i, total= 0, distribution[0] + while choice > total: + i += 1 + total += distribution[i] + return values[i] + +def sampleFromCounter(ctr): + items = sorted(ctr.items()) + return sample([v for k,v in items], [k for k,v in items]) + +def getProbability(value, distribution, values): + """ + Gives the probability of a value under a discrete distribution + defined by (distributions, values). + """ + total = 0.0 + for prob, val in zip(distribution, values): + if val == value: + total += prob + return total + +def flipCoin( p ): + r = random.random() + return r < p + +def chooseFromDistribution( distribution ): + "Takes either a counter or a list of (prob, key) pairs and samples" + if type(distribution) == dict or type(distribution) == Counter: + return sample(distribution) + r = random.random() + base = 0.0 + for prob, element in distribution: + base += prob + if r <= base: return element + +def nearestPoint( pos ): + """ + Finds the nearest grid point to a position (discretizes). + """ + ( current_row, current_col ) = pos + + grid_row = int( current_row + 0.5 ) + grid_col = int( current_col + 0.5 ) + return ( grid_row, grid_col ) + +def sign( x ): + """ + Returns 1 or -1 depending on the sign of x + """ + if( x >= 0 ): + return 1 + else: + return -1 + +def arrayInvert(array): + """ + Inverts a matrix stored as a list of lists. + """ + result = [[] for i in array] + for outer in array: + for inner in range(len(outer)): + result[inner].append(outer[inner]) + return result + +def matrixAsList( matrix, value = True ): + """ + Turns a matrix into a list of coordinates matching the specified value + """ + rows, cols = len( matrix ), len( matrix[0] ) + cells = [] + for row in range( rows ): + for col in range( cols ): + if matrix[row][col] == value: + cells.append( ( row, col ) ) + return cells + +def lookup(name, namespace): + """ + Get a method or class from any imported module from its name. + Usage: lookup(functionName, globals()) + """ + dots = name.count('.') + if dots > 0: + moduleName, objName = '.'.join(name.split('.')[:-1]), name.split('.')[-1] + module = __import__(moduleName) + return getattr(module, objName) + else: + modules = [obj for obj in namespace.values() if str(type(obj)) == ""] + options = [getattr(module, name) for module in modules if name in dir(module)] + options += [obj[1] for obj in namespace.items() if obj[0] == name ] + if len(options) == 1: return options[0] + if len(options) > 1: raise Exception, 'Name conflict for %s' + raise Exception, '%s not found as a method or class' % name + +def pause(): + """ + Pauses the output stream awaiting user feedback. + """ + print "" + raw_input() + + +# code to handle timeouts +# +# FIXME +# NOTE: TimeoutFuncton is NOT reentrant. Later timeouts will silently +# disable earlier timeouts. Could be solved by maintaining a global list +# of active time outs. Currently, questions which have test cases calling +# this have all student code so wrapped. +# +import signal +import time +class TimeoutFunctionException(Exception): + """Exception to raise on a timeout""" + pass + + +class TimeoutFunction: + def __init__(self, function, timeout): + self.timeout = timeout + self.function = function + + def handle_timeout(self, signum, frame): + raise TimeoutFunctionException() + + def __call__(self, *args, **keyArgs): + # If we have SIGALRM signal, use it to cause an exception if and + # when this function runs too long. Otherwise check the time taken + # after the method has returned, and throw an exception then. + if hasattr(signal, 'SIGALRM'): + old = signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.timeout) + try: + result = self.function(*args, **keyArgs) + finally: + signal.signal(signal.SIGALRM, old) + signal.alarm(0) + else: + startTime = time.time() + result = self.function(*args, **keyArgs) + timeElapsed = time.time() - startTime + if timeElapsed >= self.timeout: + self.handle_timeout(None, None) + return result + + + +_ORIGINAL_STDOUT = None +_ORIGINAL_STDERR = None +_MUTED = False + +class WritableNull: + def write(self, string): + pass + +def mutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if _MUTED: + return + _MUTED = True + + _ORIGINAL_STDOUT = sys.stdout + #_ORIGINAL_STDERR = sys.stderr + sys.stdout = WritableNull() + #sys.stderr = WritableNull() + +def unmutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if not _MUTED: + return + _MUTED = False + + sys.stdout = _ORIGINAL_STDOUT + #sys.stderr = _ORIGINAL_STDERR + diff --git a/search/util.pyc b/search/util.pyc new file mode 100644 index 0000000000000000000000000000000000000000..3e647d3894bba67bf05c7cee8ce7baadad670315 GIT binary patch literal 35116 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 WF2-Sm61EtfS2~FnQog?wHU2l&Is`lb literal 0 HcmV?d00001