├── planning ├── tests │ ├── __init__.py │ ├── test_my_air_cargo_problems.py │ └── test_my_planning_graph.py ├── aimacode │ ├── __init__.py │ ├── LICENSE │ ├── planning.py │ └── search.py ├── images │ ├── statespace.png │ ├── twotofive.png │ ├── Progression.PNG │ └── eatcake-graphplan2.png ├── research_review.pdf ├── heuristic_analysis.pdf ├── .udacity-pa │ └── projects.py ├── lp_utils.py ├── example_have_cake.py ├── run_search.py ├── heuristic_analysis.md ├── README.md └── my_air_cargo_problems.py ├── sudoku ├── objects │ ├── __init__.py │ ├── GameResources.py │ └── SudokuSquare.py ├── images │ └── sudoku-board-bare.jpg ├── visualize.py ├── PySudoku.py ├── README.md ├── solution.py └── solution_test.py ├── isolation ├── viz.gif ├── research_review.pdf ├── heuristic_analysis.pdf ├── isoviz │ ├── img │ │ └── chesspieces │ │ │ └── wikipedia │ │ │ ├── bN.png │ │ │ └── wN.png │ ├── LICENSE.txt │ ├── css │ │ └── chessboard.css │ ├── display.html │ └── js │ │ └── json3.min.js ├── isolation │ ├── __init__.py │ ├── README.md │ └── isolation.py ├── agent_test.py ├── competition_agent.py ├── tournament.py ├── sample_players.py └── README.md ├── recognizer ├── data │ ├── README.md │ ├── speaker.csv │ └── test_words.csv ├── asl_test_recognizer.py ├── my_recognizer.py ├── asl_test_model_selectors.py ├── README.md ├── asl_test.py ├── asl_utils.py ├── my_model_selectors.py └── asl_data.py ├── .gitignore └── README.md /planning/tests/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /sudoku/objects/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /planning/aimacode/__init__.py: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /isolation/viz.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/isolation/viz.gif -------------------------------------------------------------------------------- /isolation/research_review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/isolation/research_review.pdf -------------------------------------------------------------------------------- /planning/images/statespace.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/images/statespace.png -------------------------------------------------------------------------------- /planning/images/twotofive.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/images/twotofive.png -------------------------------------------------------------------------------- /planning/research_review.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/research_review.pdf -------------------------------------------------------------------------------- /isolation/heuristic_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/isolation/heuristic_analysis.pdf -------------------------------------------------------------------------------- /planning/heuristic_analysis.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/heuristic_analysis.pdf -------------------------------------------------------------------------------- /planning/images/Progression.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/images/Progression.PNG -------------------------------------------------------------------------------- /sudoku/images/sudoku-board-bare.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/sudoku/images/sudoku-board-bare.jpg -------------------------------------------------------------------------------- /planning/images/eatcake-graphplan2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/planning/images/eatcake-graphplan2.png -------------------------------------------------------------------------------- /isolation/isoviz/img/chesspieces/wikipedia/bN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/isolation/isoviz/img/chesspieces/wikipedia/bN.png -------------------------------------------------------------------------------- /isolation/isoviz/img/chesspieces/wikipedia/wN.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/stefan-rz/udacity-aind/HEAD/isolation/isoviz/img/chesspieces/wikipedia/wN.png -------------------------------------------------------------------------------- /sudoku/objects/GameResources.py: -------------------------------------------------------------------------------- 1 | import os, pygame 2 | 3 | def load_image(name): 4 | """A better load of images.""" 5 | fullname = os.path.join("images", name) 6 | try: 7 | image = pygame.image.load(fullname) 8 | if image.get_alpha() == None: 9 | image = image.convert() 10 | else: 11 | image = image.convert_alpha() 12 | except pygame.error: 13 | print("Oops! Could not load image:", fullname) 14 | return image, image.get_rect() 15 | -------------------------------------------------------------------------------- /isolation/isolation/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | This library provides a Python implementation of the game Isolation. 3 | Isolation is a deterministic, two-player game of perfect information in 4 | which the players alternate turns moving between cells on a square grid 5 | (like a checkerboard). Whenever either player occupies a cell, that 6 | location is blocked for the rest of the game. The first player with no 7 | legal moves loses, and the opponent is declared the winner. 8 | """ 9 | 10 | # Make the Board class available at the root of the module for imports 11 | from .isolation import Board 12 | -------------------------------------------------------------------------------- /planning/.udacity-pa/projects.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import shutil 3 | import os 4 | from udacity_pa import udacity 5 | 6 | nanodegree = 'nd889' 7 | projects = ['cargo_planning'] 8 | filenames_all = ['my_air_cargo_problems.py', 'my_planning_graph.py', 'heuristic_analysis.pdf', 'research_review.pdf'] 9 | 10 | def submit(args): 11 | filenames = [] 12 | for filename in filenames_all: 13 | if os.path.isfile(filename): 14 | filenames.append(filename) 15 | 16 | udacity.submit(nanodegree, projects[0], filenames, 17 | environment = args.environment, 18 | jwt_path = args.jwt_path) 19 | -------------------------------------------------------------------------------- /isolation/agent_test.py: -------------------------------------------------------------------------------- 1 | """This file is provided as a starting template for writing your own unit 2 | tests to run and debug your minimax and alphabeta agents locally. The test 3 | cases used by the project assistant are not public. 4 | """ 5 | 6 | import unittest 7 | 8 | import isolation 9 | import game_agent 10 | 11 | from importlib import reload 12 | 13 | 14 | class IsolationTest(unittest.TestCase): 15 | """Unit tests for isolation agents""" 16 | 17 | def setUp(self): 18 | reload(game_agent) 19 | self.player1 = "Player1" 20 | self.player2 = "Player2" 21 | self.game = isolation.Board(self.player1, self.player2) 22 | 23 | 24 | if __name__ == '__main__': 25 | unittest.main() 26 | -------------------------------------------------------------------------------- /sudoku/visualize.py: -------------------------------------------------------------------------------- 1 | from PySudoku import play 2 | 3 | def visualize_assignments(assignments): 4 | """ Visualizes the set of assignments created by the Sudoku AI""" 5 | last_assignment = None 6 | filtered_assignments = [] 7 | 8 | for i in range(len(assignments)): 9 | if last_assignment: 10 | last_assignment_items = [item for item in last_assignment.items() if len(item[1]) == 1] 11 | current_assignment_items = [item for item in assignments[i].items() if len(item[1]) == 1] 12 | shared_items = set(last_assignment_items) & set(current_assignment_items) 13 | if len(shared_items) < len(current_assignment_items): 14 | filtered_assignments.append(assignments[i]) 15 | last_assignment = assignments[i] 16 | 17 | play(filtered_assignments) 18 | -------------------------------------------------------------------------------- /recognizer/data/README.md: -------------------------------------------------------------------------------- 1 | ## American Sign Language Data 2 | The data in this directory contained in `hands_condensed.csv` and `speaker.csv` has been derived from the [RWTH-BOSTON-104 Database](http://www-i6.informatik.rwth-aachen.de/~dreuw/database-rwth-boston-104.php) The hand positions are pulled directly from the database [boston104.handpositions.rybach-forster-dreuw-2009-09-25.full.xml](boston104.handpositions.rybach-forster-dreuw-2009-09-25.full.xml). 3 | 4 | The videos are sentences with translations provided in the database. For purposes of this project, the sentences have been segmented into words based on slow motion examination of the files. These segments are provided in the `test_words.csv` and `train_words.csv` files in the form of start and end frames (inclusive). Training and Test word files have been divided as they are in the database, which is based on sentence Train and Test divisions. The hand positions file has not been divided and contains all frame information. 5 | -------------------------------------------------------------------------------- /planning/aimacode/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016 aima-python contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /isolation/isoviz/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2013 Chris Oakman 2 | http://chessboardjs.com/ 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining 5 | a copy of this software and associated documentation files (the 6 | "Software"), to deal in the Software without restriction, including 7 | without limitation the rights to use, copy, modify, merge, publish, 8 | distribute, sublicense, and/or sell copies of the Software, and to 9 | permit persons to whom the Software is furnished to do so, subject to 10 | the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be 13 | included in all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 17 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 19 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 20 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 21 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /recognizer/asl_test_recognizer.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from asl_data import AslDb 4 | from asl_utils import train_all_words 5 | from my_model_selectors import SelectorConstant 6 | from my_recognizer import recognize 7 | 8 | FEATURES = ['right-y', 'right-x'] 9 | 10 | class TestRecognize(TestCase): 11 | def setUp(self): 12 | self.asl = AslDb() 13 | self.training_set = self.asl.build_training(FEATURES) 14 | self.test_set = self.asl.build_test(FEATURES) 15 | self.models = train_all_words(self.training_set, SelectorConstant) 16 | 17 | def test_recognize_probabilities_interface(self): 18 | probs, _ = recognize(self.models, self.test_set) 19 | self.assertEqual(len(probs), self.test_set.num_items, "Number of test items in probabilities list incorrect.") 20 | self.assertIn('FRANK', probs[0], "Dictionary of probabilities does not contain correct keys") 21 | self.assertIn('CHICKEN', probs[-1], "Dictionary of probabilities does not contain correct keys") 22 | 23 | def test_recognize_guesses_interface(self): 24 | _, guesses = recognize(self.models, self.test_set) 25 | self.assertEqual(len(guesses), self.test_set.num_items, "Number of test items in guesses list incorrect.") 26 | self.assertIsInstance(guesses[0], str, "The guesses are not strings") 27 | self.assertIsInstance(guesses[-1], str, "The guesses are not strings") 28 | 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by .ignore support plugin (hsz.mobi) 2 | ### Python template 3 | # Byte-compiled / optimized / DLL files 4 | __pycache__/ 5 | *.py[cod] 6 | *$py.class 7 | 8 | # C extensions 9 | *.so 10 | 11 | # Distribution / packaging 12 | .Python 13 | env/ 14 | build/ 15 | develop-eggs/ 16 | dist/ 17 | downloads/ 18 | eggs/ 19 | .eggs/ 20 | lib/ 21 | lib64/ 22 | parts/ 23 | sdist/ 24 | var/ 25 | wheels/ 26 | *.egg-info/ 27 | .installed.cfg 28 | *.egg 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *,cover 49 | .hypothesis/ 50 | 51 | # Translations 52 | *.mo 53 | *.pot 54 | 55 | # Django stuff: 56 | *.log 57 | local_settings.py 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # dotenv 85 | .env 86 | 87 | # virtualenv 88 | .venv 89 | venv/ 90 | ENV/ 91 | 92 | # Spyder project settings 93 | .spyderproject 94 | 95 | # Rope project settings 96 | .ropeproject 97 | 98 | .gitignore 99 | .idea/ 100 | 101 | -------------------------------------------------------------------------------- /recognizer/my_recognizer.py: -------------------------------------------------------------------------------- 1 | import warnings 2 | from asl_data import SinglesData 3 | 4 | 5 | def recognize(models: dict, test_set: SinglesData): 6 | """ Recognize test word sequences from word models set 7 | 8 | :param models: dict of trained models 9 | {'SOMEWORD': GaussianHMM model object, 'SOMEOTHERWORD': GaussianHMM model object, ...} 10 | :param test_set: SinglesData object 11 | :return: (list, list) as probabilities, guesses 12 | both lists are ordered by the test set word_id 13 | probabilities is a list of dictionaries where each key a word and value is Log Liklihood 14 | [{SOMEWORD': LogLvalue, 'SOMEOTHERWORD' LogLvalue, ... }, 15 | {SOMEWORD': LogLvalue, 'SOMEOTHERWORD' LogLvalue, ... }, 16 | ] 17 | guesses is a list of the best guess words ordered by the test set word_id 18 | ['WORDGUESS0', 'WORDGUESS1', 'WORDGUESS2',...] 19 | """ 20 | warnings.filterwarnings("ignore", category=DeprecationWarning) 21 | probabilities = [] 22 | guesses = [] 23 | for _, (X, lengths) in test_set.get_all_Xlengths().items(): 24 | probability_dict = {} 25 | best_score = float("-inf") 26 | best_guess = "" 27 | for trained_word, model in models.items(): 28 | try: 29 | score = model.score(X, lengths) 30 | probability_dict[traned_word] = score 31 | except Exception as e: 32 | probability_dict[trained_word] = float("-inf") 33 | 34 | if score > best_score: 35 | best_score = score 36 | best_guess = trained_word 37 | probabilities.append(probability_dict) 38 | guesses.append(best_guess) 39 | 40 | return probabilities, guesses 41 | -------------------------------------------------------------------------------- /recognizer/asl_test_model_selectors.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from asl_data import AslDb 4 | from my_model_selectors import ( 5 | SelectorConstant, SelectorBIC, SelectorDIC, SelectorCV, 6 | ) 7 | 8 | FEATURES = ['right-y', 'right-x'] 9 | 10 | class TestSelectors(TestCase): 11 | def setUp(self): 12 | asl = AslDb() 13 | self.training = asl.build_training(FEATURES) 14 | self.sequences = self.training.get_all_sequences() 15 | self.xlengths = self.training.get_all_Xlengths() 16 | 17 | def test_select_constant_interface(self): 18 | model = SelectorConstant(self.sequences, self.xlengths, 'BUY').select() 19 | self.assertGreaterEqual(model.n_components, 2) 20 | model = SelectorConstant(self.sequences, self.xlengths, 'BOOK').select() 21 | self.assertGreaterEqual(model.n_components, 2) 22 | 23 | def test_select_bic_interface(self): 24 | model = SelectorBIC(self.sequences, self.xlengths, 'FRANK').select() 25 | self.assertGreaterEqual(model.n_components, 2) 26 | model = SelectorBIC(self.sequences, self.xlengths, 'VEGETABLE').select() 27 | self.assertGreaterEqual(model.n_components, 2) 28 | 29 | def test_select_cv_interface(self): 30 | model = SelectorCV(self.sequences, self.xlengths, 'JOHN').select() 31 | self.assertGreaterEqual(model.n_components, 2) 32 | model = SelectorCV(self.sequences, self.xlengths, 'CHICKEN').select() 33 | self.assertGreaterEqual(model.n_components, 2) 34 | 35 | def test_select_dic_interface(self): 36 | model = SelectorDIC(self.sequences, self.xlengths, 'MARY').select() 37 | self.assertGreaterEqual(model.n_components, 2) 38 | model = SelectorDIC(self.sequences, self.xlengths, 'TOY').select() 39 | self.assertGreaterEqual(model.n_components, 2) 40 | -------------------------------------------------------------------------------- /isolation/isoviz/css/chessboard.css: -------------------------------------------------------------------------------- 1 | /*! 2 | * chessboard.js v0.3.0 3 | * 4 | * Copyright 2013 Chris Oakman 5 | * Released under the MIT license 6 | * https://github.com/oakmac/chessboardjs/blob/master/LICENSE 7 | * 8 | * Date: 10 Aug 2013 9 | */ 10 | 11 | td { 12 | font-size: 1.5em; 13 | } 14 | 15 | /* clearfix */ 16 | .clearfix-7da63 { 17 | clear: both; 18 | } 19 | 20 | /* board */ 21 | .board-b72b1 { 22 | border: 2px solid #404040; 23 | -moz-box-sizing: content-box; 24 | box-sizing: content-box; 25 | } 26 | 27 | /* square */ 28 | .square-55d63 { 29 | float: left; 30 | position: relative; 31 | 32 | /* disable any native browser highlighting */ 33 | -webkit-touch-callout: none; 34 | -webkit-user-select: none; 35 | -khtml-user-select: none; 36 | -moz-user-select: none; 37 | -ms-user-select: none; 38 | user-select: none; 39 | } 40 | 41 | /* white square */ 42 | .white-1e1d7.blocked { 43 | background-color: #999999; 44 | color: #333333; 45 | } 46 | 47 | /* black square */ 48 | .black-3c85d.blocked { 49 | background-color: #333333; 50 | color: #999999; 51 | } 52 | 53 | /* white square */ 54 | .white-1e1d7 { 55 | background-color: #f0d9b5; 56 | color: #b58863; 57 | } 58 | 59 | /* black square */ 60 | .black-3c85d { 61 | background-color: #b58863; 62 | color: #f0d9b5; 63 | } 64 | 65 | /* white square */ 66 | .square-55d63.win { 67 | background-color: #339900; 68 | color: #999999; 69 | } 70 | 71 | /* black square */ 72 | .square-55d63.lose { 73 | background-color: #990000; 74 | color: #333333; 75 | } 76 | 77 | /* notation */ 78 | .notation-322f9 { 79 | cursor: default; 80 | font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; 81 | font-size: 14px; 82 | position: absolute; 83 | } 84 | .alpha-d2270 { 85 | bottom: 1px; 86 | right: 3px; 87 | } 88 | .numeric-fc462 { 89 | top: 2px; 90 | left: 2px; 91 | } -------------------------------------------------------------------------------- /sudoku/PySudoku.py: -------------------------------------------------------------------------------- 1 | import sys, os, random, pygame 2 | sys.path.append(os.path.join("objects")) 3 | import SudokuSquare 4 | from GameResources import * 5 | 6 | digits = '123456789' 7 | rows = 'ABCDEFGHI' 8 | 9 | 10 | def play(values_list): 11 | pygame.init() 12 | 13 | 14 | size = width, height = 700, 700 15 | screen = pygame.display.set_mode(size) 16 | 17 | background_image = pygame.image.load("./images/sudoku-board-bare.jpg").convert() 18 | 19 | clock = pygame.time.Clock() 20 | 21 | # The puzzleNumber sets a seed so either generate 22 | # a random number to fill in here or accept user 23 | # input for a duplicatable puzzle. 24 | 25 | for values in values_list: 26 | pygame.event.pump() 27 | theSquares = [] 28 | initXLoc = 0 29 | initYLoc = 0 30 | startX, startY, editable, number = 0, 0, "N", 0 31 | for y in range(9): 32 | for x in range(9): 33 | if x in (0, 1, 2): startX = (x * 57) + 38 34 | if x in (3, 4, 5): startX = (x * 57) + 99 35 | if x in (6, 7, 8): startX = (x * 57) + 159 36 | 37 | if y in (0, 1, 2): startY = (y * 57) + 35 38 | if y in (3, 4, 5): startY = (y * 57) + 100 39 | if y in (6, 7, 8): startY = (y * 57) + 165 40 | col = digits[x] 41 | row = rows[y] 42 | string_number = values[row + col] 43 | if len(string_number) > 1 or string_number == '' or string_number == '.': 44 | number = None 45 | else: 46 | number = int(string_number) 47 | theSquares.append(SudokuSquare.SudokuSquare(number, startX, startY, editable, x, y)) 48 | 49 | screen.blit(background_image, (0, 0)) 50 | for num in theSquares: 51 | num.draw() 52 | 53 | pygame.display.flip() 54 | pygame.display.update() 55 | clock.tick(5) 56 | 57 | # leave game showing until closed by user 58 | while True: 59 | for event in pygame.event.get(): 60 | if event.type == pygame.QUIT: 61 | pygame.quit() 62 | quit() 63 | 64 | if __name__ == "__main__": 65 | main() 66 | sys.exit() -------------------------------------------------------------------------------- /planning/lp_utils.py: -------------------------------------------------------------------------------- 1 | from aimacode.logic import associate 2 | from aimacode.utils import expr 3 | 4 | 5 | class FluentState(): 6 | """ state object for planning problems as positive and negative fluents 7 | 8 | """ 9 | 10 | def __init__(self, pos_list, neg_list): 11 | self.pos = pos_list 12 | self.neg = neg_list 13 | 14 | def sentence(self): 15 | return expr(conjunctive_sentence(self.pos, self.neg)) 16 | 17 | def pos_sentence(self): 18 | return expr(conjunctive_sentence(self.pos, [])) 19 | 20 | 21 | def conjunctive_sentence(pos_list, neg_list): 22 | """ returns expr conjuntive sentence given positive and negative fluent lists 23 | 24 | :param pos_list: list of fluents 25 | :param neg_list: list of fluents 26 | :return: expr sentence of fluent conjunction 27 | e.g. "At(C1, SFO) ∧ ~At(P1, SFO)" 28 | """ 29 | clauses = [] 30 | for f in pos_list: 31 | clauses.append(expr("{}".format(f))) 32 | for f in neg_list: 33 | clauses.append(expr("~{}".format(f))) 34 | return associate('&', clauses) 35 | 36 | 37 | def encode_state(fs: FluentState, fluent_map: list) -> str: 38 | """ encode fluents to a string of T/F using mapping 39 | 40 | :param fs: FluentState object 41 | :param fluent_map: ordered list of possible fluents for the problem 42 | :return: str eg. "TFFTFT" string of mapped positive and negative fluents 43 | """ 44 | state_tf = [] 45 | for fluent in fluent_map: 46 | if fluent in fs.pos: 47 | state_tf.append('T') 48 | else: 49 | state_tf.append('F') 50 | return "".join(state_tf) 51 | 52 | 53 | def decode_state(state: str, fluent_map: list) -> FluentState: 54 | """ decode string of T/F as fluent per mapping 55 | 56 | :param state: str eg. "TFFTFT" string of mapped positive and negative fluents 57 | :param fluent_map: ordered list of possible fluents for the problem 58 | :return: fs: FluentState object 59 | 60 | lengths of state string and fluent_map list must be the same 61 | """ 62 | fs = FluentState([], []) 63 | for idx, char in enumerate(state): 64 | if char == 'T': 65 | fs.pos.append(fluent_map[idx]) 66 | else: 67 | fs.neg.append(fluent_map[idx]) 68 | return fs 69 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [Udacity Artificial Intelligence Nanodegree](https://www.udacity.com/course/artificial-intelligence-nanodegree--nd889) 2 | A repository of multiple projects and labs done in the Udacity Artificial Intelligence Nanodegree (aind). 3 | 4 | ### [Sudoku](/sudoku) 5 | 6 | In this project, you will be writing code to implement two extensions of our sudoku solver. The first one will be to implement the technique called "naked twins". The second one will be to modify our existing code to solve a diagonal sudoku. To complete this project you will use the tools you learned about in the lesson, and build upon them. 7 | 8 | Your goals are to implement the naked twins function, and write an AI agent that will solve the Diagonal Sudoku game. 9 | 10 | ### [Build a Game-playing Agent(Isolation)](/isolation) 11 | In this project, students will develop an adversarial search agent to play the game "Isolation". Isolation is a deterministic, two-player game of perfect information in which the players alternate turns moving a single piece from one cell to another on a board. Whenever either player occupies a cell, that cell becomes blocked for the remainder of the game. The first player with no remaining legal moves loses, and the opponent is declared the winner. These rules are implemented in the isolation.Board class provided in the repository. 12 | 13 | ### [Lab: PAC-MAN](https://github.com/ruihanzou/Teaching-Pac-Man-to-Search) 14 | 15 | In this lab, you will teach Pac-Man to search his world to complete the following tasks: 16 | 17 | find a single obstacle. 18 | find multiple obstacles. 19 | find the fastest way to eat all the food in the map. 20 | 21 | ### [Planning](/planning) 22 | 23 | In this project, you will define a group of problems in classical PDDL (Planning Domain Definition Language) for the air cargo domain discussed in the lectures. You will then set up the problems for search, experiment with various automatically generated heuristics, including planning graph heuristics, to solve the problems, and then provide an analysis of the results. Additionally, you will write a short research review paper on the historical development of planning techniques and their use in artificial intelligence. 24 | 25 | ### [Recognizer](/recognizer) 26 | 27 | In this project, you will build a system that can recognize words communicated using the American Sign Language (ASL). You will be provided a preprocessed dataset of tracked hand and nose positions extracted from video. Your goal would be to train a set of Hidden Markov Models (HMMs) using part of this dataset to try and identify individual words from test sequences. 28 | -------------------------------------------------------------------------------- /planning/aimacode/planning.py: -------------------------------------------------------------------------------- 1 | """Planning (Chapters 10-11) 2 | """ 3 | 4 | from .utils import Expr 5 | 6 | 7 | class Action: 8 | """ 9 | Defines an action schema using preconditions and effects 10 | Use this to describe actions in PDDL 11 | action is an Expr where variables are given as arguments(args) 12 | Precondition and effect are both lists with positive and negated literals 13 | Example: 14 | precond_pos = [expr("Human(person)"), expr("Hungry(Person)")] 15 | precond_neg = [expr("Eaten(food)")] 16 | effect_add = [expr("Eaten(food)")] 17 | effect_rem = [expr("Hungry(person)")] 18 | eat = Action(expr("Eat(person, food)"), [precond_pos, precond_neg], [effect_add, effect_rem]) 19 | """ 20 | 21 | def __init__(self, action, precond, effect): 22 | self.name = action.op 23 | self.args = action.args 24 | self.precond_pos = precond[0] 25 | self.precond_neg = precond[1] 26 | self.effect_add = effect[0] 27 | self.effect_rem = effect[1] 28 | 29 | def __call__(self, kb, args): 30 | return self.act(kb, args) 31 | 32 | def __str__(self): 33 | return "{}{!s}".format(self.name, self.args) 34 | 35 | def substitute(self, e, args): 36 | """Replaces variables in expression with their respective Propostional symbol""" 37 | new_args = list(e.args) 38 | for num, x in enumerate(e.args): 39 | for i in range(len(self.args)): 40 | if self.args[i] == x: 41 | new_args[num] = args[i] 42 | return Expr(e.op, *new_args) 43 | 44 | def check_precond(self, kb, args): 45 | """Checks if the precondition is satisfied in the current state""" 46 | # check for positive clauses 47 | for clause in self.precond_pos: 48 | if self.substitute(clause, args) not in kb.clauses: 49 | return False 50 | # check for negative clauses 51 | for clause in self.precond_neg: 52 | if self.substitute(clause, args) in kb.clauses: 53 | return False 54 | return True 55 | 56 | def act(self, kb, args): 57 | """Executes the action on the state's kb""" 58 | # check if the preconditions are satisfied 59 | if not self.check_precond(kb, args): 60 | raise Exception("Action pre-conditions not satisfied") 61 | # remove negative literals 62 | for clause in self.effect_rem: 63 | kb.retract(self.substitute(clause, args)) 64 | # add positive literals 65 | for clause in self.effect_add: 66 | kb.tell(self.substitute(clause, args)) 67 | -------------------------------------------------------------------------------- /sudoku/README.md: -------------------------------------------------------------------------------- 1 | # Artificial Intelligence Nanodegree 2 | 3 | 4 | ## Introductory Project: Diagonal Sudoku Solver 5 | 6 | # Question 1 (Naked Twins) 7 | Q: How do we use constraint propagation to solve the naked twins problem? 8 | A: To solve the naked twins problem we apply the following steps: 9 | * From current sudoku board, we call naked_twins() to identify all naked twins grouped by unit (the complete rows, columns and 3x3 squares). 10 | * In each unit, recursively remove naked twins digits from the possible value of boxes (the individual squares at the intersection of rows and columns) so that no squares outside the two naked twins squares can contain the twins values. 11 | * We depth first search all boxes and repeatedly apply naked twins technique to narrow the search space of possible solutions iteratively. 12 | 13 | # Question 2 (Diagonal Sudoku) 14 | Q: How do we use constraint propagation to solve the diagonal sudoku problem? 15 | A: To solve the diagonal sudoku problem we apply the following steps: 16 | * Consider diagonal and reverse diagonal along with row, column, square units as part of constraints 17 | * apply elimination strategy, only choice strategy, and naked twins technique repeatedly 18 | 19 | ### Install 20 | 21 | This project requires **Python 3**. 22 | 23 | We recommend students install [Anaconda](https://www.continuum.io/downloads), a pre-packaged Python distribution that contains all of the necessary libraries and software for this project. 24 | Please try using the environment we provided in the Anaconda lesson of the Nanodegree. 25 | 26 | ##### Optional: Pygame 27 | 28 | Optionally, you can also install pygame if you want to see your visualization. If you've followed our instructions for setting up our conda environment, you should be all set. 29 | 30 | If not, please see how to download pygame [here](http://www.pygame.org/download.shtml). 31 | 32 | ### Code 33 | 34 | * `solution.py` - You'll fill this in as part of your solution. 35 | * `solution_test.py` - Do not modify this. You can test your solution by running `python solution_test.py`. 36 | * `PySudoku.py` - Do not modify this. This is code for visualizing your solution. 37 | * `visualize.py` - Do not modify this. This is code for visualizing your solution. 38 | 39 | ### Visualizing 40 | 41 | To visualize your solution, please only assign values to the values_dict using the ```assign_values``` function provided in solution.py 42 | 43 | ### Code Review 44 | You can find my project feedback from one of the Udacity reviewers in [here](https://review.udacity.com/#!/reviews/451359/shared) -------------------------------------------------------------------------------- /planning/tests/test_my_air_cargo_problems.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | parent = os.path.dirname(os.path.realpath(__file__)) 4 | sys.path.append(os.path.join(os.path.dirname(parent), "aimacode")) 5 | from aimacode.planning import Action 6 | from aimacode.utils import expr 7 | from aimacode.search import Node 8 | import unittest 9 | from lp_utils import decode_state 10 | from my_air_cargo_problems import ( 11 | air_cargo_p1, air_cargo_p2, air_cargo_p3, 12 | ) 13 | 14 | class TestAirCargoProb1(unittest.TestCase): 15 | 16 | def setUp(self): 17 | self.p1 = air_cargo_p1() 18 | 19 | def test_ACP1_num_fluents(self): 20 | self.assertEqual(len(self.p1.initial), 12) 21 | 22 | def test_ACP1_num_requirements(self): 23 | self.assertEqual(len(self.p1.goal),2) 24 | 25 | 26 | class TestAirCargoProb2(unittest.TestCase): 27 | 28 | def setUp(self): 29 | self.p2 = air_cargo_p2() 30 | 31 | def test_ACP2_num_fluents(self): 32 | self.assertEqual(len(self.p2.initial), 27) 33 | 34 | def test_ACP2_num_requirements(self): 35 | self.assertEqual(len(self.p2.goal),3) 36 | 37 | 38 | class TestAirCargoProb3(unittest.TestCase): 39 | 40 | def setUp(self): 41 | self.p3 = air_cargo_p3() 42 | 43 | def test_ACP3_num_fluents(self): 44 | self.assertEqual(len(self.p3.initial), 32) 45 | 46 | def test_ACP3_num_requirements(self): 47 | self.assertEqual(len(self.p3.goal),4) 48 | 49 | 50 | class TestAirCargoMethods(unittest.TestCase): 51 | 52 | def setUp(self): 53 | self.p1 = air_cargo_p1() 54 | self.act1 = Action( 55 | expr('Load(C1, P1, SFO)'), 56 | [[expr('At(C1, SFO)'), expr('At(P1, SFO)')], []], 57 | [[expr('In(C1, P1)')], [expr('At(C1, SFO)')]] 58 | ) 59 | 60 | def test_AC_get_actions(self): 61 | # to see a list of the actions, uncomment below 62 | # print("\nactions for problem") 63 | # for action in self.p1.actions_list: 64 | # print("{}{}".format(action.name, action.args)) 65 | self.assertEqual(len(self.p1.actions_list), 20) 66 | 67 | def test_AC_actions(self): 68 | # to see list of possible actions, uncomment below 69 | # print("\npossible actions:") 70 | # for action in self.p1.actions(self.p1.initial): 71 | # print("{}{}".format(action.name, action.args)) 72 | self.assertEqual(len(self.p1.actions(self.p1.initial)), 4) 73 | 74 | def test_AC_result(self): 75 | fs = decode_state(self.p1.result(self.p1.initial, self.act1), self.p1.state_map) 76 | self.assertTrue(expr('In(C1, P1)') in fs.pos) 77 | self.assertTrue(expr('At(C1, SFO)') in fs.neg) 78 | 79 | def test_h_ignore_preconditions(self): 80 | n = Node(self.p1.initial) 81 | self.assertEqual(self.p1.h_ignore_preconditions(n),2) 82 | 83 | if __name__ == '__main__': 84 | unittest.main() 85 | -------------------------------------------------------------------------------- /isolation/isolation/README.md: -------------------------------------------------------------------------------- 1 | 2 | # isolation.Board class 3 | 4 | ## Constructor 5 | 6 | Board.__init__(self, player_1, player_2, width=7, height=7) 7 | 8 | ## Attributes 9 | 10 | ### BLANK : 0 (constant) 11 | 12 | ### NOT_MOVED : None (constant) 13 | 14 | ### width : 7 (constant) 15 | 16 | Board width 17 | 18 | ### height : 7 (constant) 19 | 20 | Board height 21 | 22 | ### active_player : hashable 23 | 24 | Reference to a hashable object registered as a player with the initiative to move on the current board 25 | 26 | ### inactive_player : hashable 27 | 28 | Reference to a hashable object registered as a player awaiting initiative to move on the current board 29 | 30 | ### move_count : int 31 | 32 | Counter indicating the number of moves that have been applied to the game 33 | 34 | ## Public Methods 35 | 36 | ### apply_move(self, move) 37 | 38 | Modify the game object by moving the active player on the game board and disabling the vacated square (if any). The forecast_move method performs the same function, but returns a copy of the board, rather than modifying the state in-place. 39 | 40 | ### copy(self) 41 | 42 | Return a new Board object that is a copy of the current game state 43 | 44 | ### forecast_move(self, move) 45 | 46 | Equivalent to apply_move, but returns a copy of the board rather than modifying the state in-place. 47 | 48 | ### get_blank_spaces(self) 49 | 50 | Returns a list of tuples identifying the blank squares on the current board 51 | 52 | ### get_legal_moves(self, player=None) 53 | 54 | Returns a list of tuples identifying the legal moves for the specified player 55 | 56 | ### get_opponent(self, player) 57 | 58 | Returns the opponent of the specified player 59 | 60 | ### get_player_location(self, player) 61 | 62 | Returns a tuple (x, y) identifying the location of the specified player on the game board, or None of the player is a registered agent in the game but has not yet been placed on the board. Raises a RuntimeError if the specified player is not registered on the board. 63 | 64 | ### hash(self) 65 | 66 | Return a hash of the current state (public alias of __hash__ method). The hashed state includes occupied cells, current player locations, and which player has initiative on the board. An equivalent hash function can be added to the isolation.Board class from the isolation project: 67 | 68 | ### is_loser(self, player) 69 | 70 | Returns True if the specified player has lost the game in the current state, and False otherwise 71 | 72 | ### is_winner(self, player) 73 | 74 | Returns True if the specified player has won the game in the current state, and False otherwise 75 | 76 | ### move_is_legal(self, move) 77 | 78 | Returns True if the active player can legally make the specified move and False otherwise 79 | 80 | ### to_string(self, symbols=['1', '2']) 81 | 82 | Return a string representation of the current board position 83 | 84 | ### utility(self, player) 85 | 86 | Returns a floating point value: +inf if the specified player has won the game, -inf if the specified player has lost the game, and 0 otherwise. -------------------------------------------------------------------------------- /recognizer/data/speaker.csv: -------------------------------------------------------------------------------- 1 | video,speaker 2 | 1,woman-1 3 | 3,woman-2 4 | 4,woman-1 5 | 5,woman-2 6 | 6,woman-2 7 | 8,man-1 8 | 9,woman-2 9 | 10,woman-1 10 | 11,woman-2 11 | 13,woman-2 12 | 14,woman-2 13 | 15,woman-2 14 | 16,man-1 15 | 17,woman-2 16 | 18,woman-1 17 | 19,woman-2 18 | 20,woman-2 19 | 22,woman-2 20 | 23,woman-2 21 | 24,woman-2 22 | 26,woman-2 23 | 27,woman-2 24 | 29,man-1 25 | 31,man-1 26 | 32,woman-2 27 | 33,woman-2 28 | 34,woman-2 29 | 35,man-1 30 | 37,woman-2 31 | 38,woman-2 32 | 39,man-1 33 | 41,woman-2 34 | 42,woman-1 35 | 44,woman-2 36 | 45,woman-1 37 | 46,woman-1 38 | 47,woman-2 39 | 48,woman-1 40 | 49,woman-1 41 | 51,woman-1 42 | 52,woman-1 43 | 53,woman-2 44 | 55,woman-2 45 | 56,man-1 46 | 58,man-1 47 | 59,woman-1 48 | 60,woman-1 49 | 61,woman-1 50 | 62,woman-1 51 | 63,woman-1 52 | 64,woman-1 53 | 65,woman-1 54 | 66,woman-1 55 | 68,woman-2 56 | 69,woman-2 57 | 70,woman-2 58 | 72,woman-2 59 | 73,man-1 60 | 75,woman-2 61 | 76,woman-2 62 | 78,woman-2 63 | 79,woman-2 64 | 80,woman-2 65 | 81,woman-2 66 | 82,woman-1 67 | 83,woman-1 68 | 85,woman-1 69 | 86,woman-1 70 | 87,woman-2 71 | 88,woman-2 72 | 91,woman-2 73 | 93,man-1 74 | 94,woman-2 75 | 95,man-1 76 | 96,man-1 77 | 97,woman-2 78 | 98,woman-1 79 | 99,woman-1 80 | 101,woman-1 81 | 102,woman-2 82 | 103,woman-1 83 | 104,woman-1 84 | 106,man-1 85 | 109,woman-2 86 | 110,woman-2 87 | 111,woman-2 88 | 112,man-1 89 | 114,man-1 90 | 115,woman-2 91 | 116,man-1 92 | 117,man-1 93 | 118,man-1 94 | 120,man-1 95 | 121,woman-2 96 | 123,woman-1 97 | 124,man-1 98 | 125,woman-1 99 | 126,woman-1 100 | 127,man-1 101 | 128,woman-1 102 | 129,man-1 103 | 130,man-1 104 | 131,man-1 105 | 132,man-1 106 | 133,man-1 107 | 134,woman-1 108 | 135,woman-1 109 | 136,woman-1 110 | 137,man-1 111 | 138,woman-1 112 | 140,woman-1 113 | 141,woman-1 114 | 143,man-1 115 | 144,woman-1 116 | 145,woman-1 117 | 146,woman-1 118 | 147,woman-1 119 | 148,man-1 120 | 149,man-1 121 | 150,woman-1 122 | 151,woman-1 123 | 152,man-1 124 | 153,man-1 125 | 154,woman-1 126 | 155,man-1 127 | 156,woman-1 128 | 157,man-1 129 | 159,man-1 130 | 160,woman-1 131 | 161,woman-1 132 | 162,woman-1 133 | 163,woman-1 134 | 164,woman-1 135 | 165,man-1 136 | 166,man-1 137 | 168,man-1 138 | 169,man-1 139 | 170,man-1 140 | 172,man-1 141 | 173,man-1 142 | 175,man-1 143 | 176,man-1 144 | 177,man-1 145 | 178,man-1 146 | 179,man-1 147 | 180,man-1 148 | 182,man-1 149 | 183,man-1 150 | 185,man-1 151 | 186,man-1 152 | 187,man-1 153 | 188,man-1 154 | 190,man-1 155 | 191,man-1 156 | 192,man-1 157 | 194,woman-1 158 | 195,woman-2 159 | 196,man-1 160 | 197,man-1 161 | 198,man-1 162 | 200,woman-1 163 | 2,woman-1 164 | 7,man-1 165 | 12,woman-2 166 | 21,woman-2 167 | 25,woman-2 168 | 28,woman-2 169 | 30,man-1 170 | 36,man-1 171 | 40,man-1 172 | 43,woman-1 173 | 50,woman-1 174 | 54,woman-2 175 | 57,man-1 176 | 67,woman-1 177 | 71,woman-2 178 | 74,man-1 179 | 77,woman-2 180 | 84,woman-1 181 | 89,woman-2 182 | 90,man-1 183 | 92,woman-2 184 | 100,woman-1 185 | 105,woman-1 186 | 107,man-1 187 | 108,woman-2 188 | 113,man-1 189 | 119,man-1 190 | 122,woman-2 191 | 139,man-1 192 | 142,woman-1 193 | 158,man-1 194 | 167,man-1 195 | 171,man-1 196 | 174,man-1 197 | 181,man-1 198 | 184,man-1 199 | 189,man-1 200 | 193,man-1 201 | 199,woman-1 202 | 201,woman-2 203 | -------------------------------------------------------------------------------- /recognizer/README.md: -------------------------------------------------------------------------------- 1 | # Artificial Intelligence Engineer Nanodegree 2 | ## Probabilistic Models 3 | ## Project: Sign Language Recognition System 4 | 5 | ### Install 6 | 7 | This project requires **Python 3** and the following Python libraries installed: 8 | 9 | - [NumPy](http://www.numpy.org/) 10 | - [SciPy](https://www.scipy.org/) 11 | - [scikit-learn](http://scikit-learn.org/0.17/install.html) 12 | - [pandas](http://pandas.pydata.org/) 13 | - [matplotlib](http://matplotlib.org/) 14 | - [jupyter](http://ipython.org/notebook.html) 15 | - [hmmlearn](http://hmmlearn.readthedocs.io/en/latest/) 16 | 17 | Notes: 18 | 1. It is highly recommended that you install the [Anaconda](http://continuum.io/downloads) distribution of Python and load the environment included in the "Your conda env for AI ND" lesson. 19 | 2. The most recent development version of hmmlearn, 0.2.1, contains a bugfix related to the log function, which is used in this project. In order to install this version of hmmearn, install it directly from its repo with the following command from within your activated Anaconda environment: 20 | ```sh 21 | pip install git+https://github.com/hmmlearn/hmmlearn.git 22 | ``` 23 | 24 | ### Code 25 | 26 | A template notebook is provided as `asl_recognizer.ipynb`. The notebook is a combination tutorial and submission document. Some of the codebase and some of your implementation will be external to the notebook. For submission, complete the **Submission** sections of each part. This will include running your implementations in code notebook cells, answering analysis questions, and passing provided unit tests provided in the codebase and called out in the notebook. 27 | 28 | ### Run 29 | 30 | In a terminal or command window, navigate to the top-level project directory `AIND_recognizer/` (that contains this README) and run one of the following command: 31 | 32 | `jupyter notebook asl_recognizer.ipynb` 33 | 34 | This will open the Jupyter Notebook software and notebook in your browser which is where you will directly edit and run your code. Follow the instructions in the notebook for completing the project. 35 | 36 | 37 | ### Additional Information 38 | ##### Provided Raw Data 39 | 40 | The data in the `asl_recognizer/data/` directory was derived from 41 | the [RWTH-BOSTON-104 Database](http://www-i6.informatik.rwth-aachen.de/~dreuw/database-rwth-boston-104.php). 42 | The handpositions (`hand_condensed.csv`) are pulled directly from 43 | the database [boston104.handpositions.rybach-forster-dreuw-2009-09-25.full.xml](boston104.handpositions.rybach-forster-dreuw-2009-09-25.full.xml). The three markers are: 44 | 45 | * 0 speaker's left hand 46 | * 1 speaker's right hand 47 | * 2 speaker's nose 48 | * X and Y values of the video frame increase left to right and top to bottom. 49 | 50 | Take a look at the sample [ASL recognizer video](http://www-i6.informatik.rwth-aachen.de/~dreuw/download/021.avi) 51 | to see how the hand locations are tracked. 52 | 53 | The videos are sentences with translations provided in the database. 54 | For purposes of this project, the sentences have been pre-segmented into words 55 | based on slow motion examination of the files. 56 | These segments are provided in the `train_words.csv` and `test_words.csv` files 57 | in the form of start and end frames (inclusive). 58 | 59 | The videos in the corpus include recordings from three different ASL speakers. 60 | The mappings for the three speakers to video are included in the `speaker.csv` 61 | file. 62 | ### Code Review 63 | You can find my project feedback from one of the Udacity reviewers in [here](https://review.udacity.com/#!/reviews/584428/shared) -------------------------------------------------------------------------------- /recognizer/asl_test.py: -------------------------------------------------------------------------------- 1 | from unittest import TestCase 2 | 3 | from asl_data import AslDb 4 | from asl_utils import train_all_words 5 | from my_model_selectors import ( 6 | SelectorConstant, SelectorBIC, SelectorDIC, SelectorCV, 7 | ) 8 | from my_recognizer import recognize 9 | 10 | """ DEPRECATED MODULE 11 | This module has been split into two new modules: asl_test_model_selectors.py and asl_test_recognizer.py 12 | This module is included in the repo for the sake of legacy code that still uses it. 13 | """ 14 | FEATURES = ['right-y', 'right-x'] 15 | 16 | 17 | class TestSelectors(TestCase): 18 | def setUp(self): 19 | asl = AslDb() 20 | self.training = asl.build_training(FEATURES) 21 | self.sequences = self.training.get_all_sequences() 22 | self.xlengths = self.training.get_all_Xlengths() 23 | 24 | def test_select_constant_interface(self): 25 | model = SelectorConstant(self.sequences, self.xlengths, 'BUY').select() 26 | self.assertGreaterEqual(model.n_components, 2) 27 | model = SelectorConstant(self.sequences, self.xlengths, 'BOOK').select() 28 | self.assertGreaterEqual(model.n_components, 2) 29 | 30 | def test_select_bic_interface(self): 31 | model = SelectorBIC(self.sequences, self.xlengths, 'FRANK').select() 32 | self.assertGreaterEqual(model.n_components, 2) 33 | model = SelectorBIC(self.sequences, self.xlengths, 'VEGETABLE').select() 34 | self.assertGreaterEqual(model.n_components, 2) 35 | 36 | def test_select_cv_interface(self): 37 | model = SelectorCV(self.sequences, self.xlengths, 'JOHN').select() 38 | self.assertGreaterEqual(model.n_components, 2) 39 | model = SelectorCV(self.sequences, self.xlengths, 'CHICKEN').select() 40 | self.assertGreaterEqual(model.n_components, 2) 41 | 42 | def test_select_dic_interface(self): 43 | model = SelectorDIC(self.sequences, self.xlengths, 'MARY').select() 44 | self.assertGreaterEqual(model.n_components, 2) 45 | model = SelectorDIC(self.sequences, self.xlengths, 'TOY').select() 46 | self.assertGreaterEqual(model.n_components, 2) 47 | 48 | 49 | class TestRecognize(TestCase): 50 | def setUp(self): 51 | self.asl = AslDb() 52 | self.training_set = self.asl.build_training(FEATURES) 53 | self.test_set = self.asl.build_test(FEATURES) 54 | self.models = train_all_words(self.training_set, SelectorConstant) 55 | 56 | def test_recognize_probabilities_interface(self): 57 | probs, _ = recognize(self.models, self.test_set) 58 | self.assertEqual(len(probs), self.test_set.num_items, "Number of test items in probabilities list incorrect.") 59 | self.assertEqual(len(probs[0]), self.training_set.num_items, 60 | "Number of training word probabilities in test item dictionary incorrect.") 61 | self.assertEqual(len(probs[-1]), self.training_set.num_items, 62 | "Number of training word probabilities in test item dictionary incorrect.") 63 | self.assertIn('FRANK', probs[0], "Dictionary of probabilities does not contain correct keys") 64 | self.assertIn('CHICKEN', probs[-1], "Dictionary of probabilities does not contain correct keys") 65 | 66 | def test_recognize_guesses_interface(self): 67 | _, guesses = recognize(self.models, self.test_set) 68 | self.assertEqual(len(guesses), self.test_set.num_items, "Number of test items in guesses list incorrect.") 69 | self.assertIsInstance(guesses[0], str, "The guesses are not strings") 70 | self.assertIsInstance(guesses[-1], str, "The guesses are not strings") 71 | -------------------------------------------------------------------------------- /isolation/competition_agent.py: -------------------------------------------------------------------------------- 1 | """Implement your own custom search agent using any combination of techniques 2 | you choose. This agent will compete against other students (and past 3 | champions) in a tournament. 4 | 5 | COMPLETING AND SUBMITTING A COMPETITION AGENT IS OPTIONAL 6 | """ 7 | import random 8 | 9 | 10 | class SearchTimeout(Exception): 11 | """Subclass base exception for code clarity. """ 12 | pass 13 | 14 | 15 | def custom_score(game, player): 16 | """Calculate the heuristic value of a game state from the point of view 17 | of the given player. 18 | 19 | This should be the best heuristic function for your project submission. 20 | 21 | Parameters 22 | ---------- 23 | game : `isolation.Board` 24 | An instance of `isolation.Board` encoding the current state of the 25 | game (e.g., player locations and blocked cells). 26 | 27 | player : object 28 | A player instance in the current game (i.e., an object corresponding to 29 | one of the player objects `game.__player_1__` or `game.__player_2__`.) 30 | 31 | Returns 32 | ------- 33 | float 34 | The heuristic value of the current game state to the specified player. 35 | """ 36 | raise NotImplementedError 37 | 38 | 39 | class CustomPlayer: 40 | """Game-playing agent to use in the optional player vs player Isolation 41 | competition. 42 | 43 | You must at least implement the get_move() method and a search function 44 | to complete this class, but you may use any of the techniques discussed 45 | in lecture or elsewhere on the web -- opening books, MCTS, etc. 46 | 47 | ************************************************************************** 48 | THIS CLASS IS OPTIONAL -- IT IS ONLY USED IN THE ISOLATION PvP 49 | COMPETITION. IT IS NOT REQUIRED FOR THE ISOLATION PROJECT REVIEW. 50 | ************************************************************************** 51 | 52 | Parameters 53 | ---------- 54 | data : string 55 | The name of the search method to use in get_move(). 56 | 57 | timeout : float (optional) 58 | Time remaining (in milliseconds) when search is aborted. Note that 59 | the PvP competition uses more accurate timers that are not cross- 60 | platform compatible, so a limit of 1ms (vs 10ms for the other classes) 61 | is generally sufficient. 62 | """ 63 | 64 | def __init__(self, data=None, timeout=1.): 65 | self.score = custom_score 66 | self.time_left = None 67 | self.TIMER_THRESHOLD = timeout 68 | 69 | def get_move(self, game, time_left): 70 | """Search for the best move from the available legal moves and return a 71 | result before the time limit expires. 72 | 73 | ********************************************************************** 74 | NOTE: If time_left < 0 when this function returns, the agent will 75 | forfeit the game due to timeout. You must return _before_ the 76 | timer reaches 0. 77 | ********************************************************************** 78 | 79 | Parameters 80 | ---------- 81 | game : `isolation.Board` 82 | An instance of `isolation.Board` encoding the current state of the 83 | game (e.g., player locations and blocked cells). 84 | 85 | time_left : callable 86 | A function that returns the number of milliseconds left in the 87 | current turn. Returning with any less than 0 ms remaining forfeits 88 | the game. 89 | 90 | Returns 91 | ------- 92 | (int, int) 93 | Board coordinates corresponding to a legal move; may return 94 | (-1, -1) if there are no available legal moves. 95 | """ 96 | # OPTIONAL: Finish this function! 97 | raise NotImplementedError 98 | -------------------------------------------------------------------------------- /sudoku/objects/SudokuSquare.py: -------------------------------------------------------------------------------- 1 | import pygame 2 | 3 | from pygame import * 4 | 5 | def AAfilledRoundedRect(surface,rect,color,radius=0.4): 6 | 7 | """ 8 | AAfilledRoundedRect(surface,rect,color,radius=0.4) 9 | 10 | surface : destination 11 | rect : rectangle 12 | color : rgb or rgba 13 | radius : 0 <= radius <= 1 14 | """ 15 | 16 | rect = Rect(rect) 17 | color = Color(*color) 18 | alpha = color.a 19 | color.a = 0 20 | pos = rect.topleft 21 | rect.topleft = 0,0 22 | rectangle = Surface(rect.size,SRCALPHA) 23 | 24 | circle = Surface([min(rect.size)*3]*2,SRCALPHA) 25 | draw.ellipse(circle,(0,0,0),circle.get_rect(),0) 26 | circle = transform.smoothscale(circle,[int(min(rect.size)*radius)]*2) 27 | 28 | radius = rectangle.blit(circle,(0,0)) 29 | radius.bottomright = rect.bottomright 30 | rectangle.blit(circle,radius) 31 | radius.topright = rect.topright 32 | rectangle.blit(circle,radius) 33 | radius.bottomleft = rect.bottomleft 34 | rectangle.blit(circle,radius) 35 | 36 | rectangle.fill((0,0,0),rect.inflate(-radius.w,0)) 37 | rectangle.fill((0,0,0),rect.inflate(0,-radius.h)) 38 | 39 | rectangle.fill(color,special_flags=BLEND_RGBA_MAX) 40 | rectangle.fill((255,255,255,alpha),special_flags=BLEND_RGBA_MIN) 41 | 42 | return surface.blit(rectangle,pos) 43 | 44 | class SudokuSquare: 45 | """A sudoku square class.""" 46 | def __init__(self, number=None, offsetX=0, offsetY=0, edit="Y", xLoc=0, yLoc=0): 47 | if number != None: 48 | number = str(number) 49 | self.color = (2, 204, 186) 50 | else: 51 | number = "" 52 | self.color = (255, 255, 255) 53 | # print("FONTS", pygame.font.get_fonts()) 54 | self.font = pygame.font.SysFont('opensans', 21) 55 | self.text = self.font.render(number, 1, (255, 255, 255)) 56 | self.textpos = self.text.get_rect() 57 | self.textpos = self.textpos.move(offsetX + 17, offsetY + 4) 58 | 59 | # self.collide = pygame.Surface((25, 22)) 60 | # self.collide = self.collide.convert() 61 | # AAfilledRoundedRect(pygame.display.get_surface(), (xLoc, yLoc, 25, 22), (255, 255, 255)) 62 | # self.collide.fill((2, 204, 186)) 63 | # self.collideRect = self.collide.get_rect() 64 | # self.collideRect = self.collideRect.move(offsetX + 1, offsetY + 1) 65 | # The rect around the text is 11 x 28 66 | 67 | self.edit = edit 68 | self.xLoc = xLoc 69 | self.yLoc = yLoc 70 | self.offsetX = offsetX 71 | self.offsetY = offsetY 72 | 73 | def draw(self): 74 | screen = pygame.display.get_surface() 75 | AAfilledRoundedRect(screen, (self.offsetX, self.offsetY, 45, 40), self.color) 76 | 77 | # screen.blit(self.collide, self.collideRect) 78 | screen.blit(self.text, self.textpos) 79 | 80 | 81 | def checkCollide(self, collision): 82 | if len(collision) == 2: 83 | return self.collideRect.collidepoint(collision) 84 | elif len(collision) == 4: 85 | return self.collideRect.colliderect(collision) 86 | else: 87 | return False 88 | 89 | 90 | def highlight(self): 91 | self.collide.fill((190, 190, 255)) 92 | self.draw() 93 | 94 | 95 | def unhighlight(self): 96 | self.collide.fill((255, 255, 255, 255)) 97 | self.draw() 98 | 99 | 100 | def change(self, number): 101 | if number != None: 102 | number = str(number) 103 | else: 104 | number = "" 105 | 106 | if self.edit == "Y": 107 | self.text = self.font.render(number, 1, (0, 0, 0)) 108 | self.draw() 109 | return 0 110 | else: 111 | return 1 112 | 113 | 114 | def currentLoc(self): 115 | return self.xLoc, self.yLoc -------------------------------------------------------------------------------- /isolation/isoviz/display.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Empty Board Example 7 | 8 | 9 | 27 | 28 | 29 | 30 |
31 |
32 | Player1:
33 | 34 |
35 | Player2:
36 | 37 |
38 | Move History:
39 | 40 |
41 | 42 |
43 |
44 | 45 |
46 |
47 |
48 |
49 |
50 |
51 | 52 | 53 | 54 | 55 | 138 | 139 | -------------------------------------------------------------------------------- /recognizer/asl_utils.py: -------------------------------------------------------------------------------- 1 | from asl_data import SinglesData, WordsData 2 | import numpy as np 3 | from IPython.core.display import display, HTML 4 | 5 | RAW_FEATURES = ['left-x', 'left-y', 'right-x', 'right-y'] 6 | GROUND_FEATURES = ['grnd-rx', 'grnd-ry', 'grnd-lx', 'grnd-ly'] 7 | 8 | 9 | def show_errors(guesses: list, test_set: SinglesData): 10 | """ Print WER and sentence differences in tabular form 11 | 12 | :param guesses: list of test item answers, ordered 13 | :param test_set: SinglesData object 14 | :return: 15 | nothing returned, prints error report 16 | 17 | WER = (S+I+D)/N but we have no insertions or deletions for isolated words so WER = S/N 18 | """ 19 | S = 0 20 | N = len(test_set.wordlist) 21 | num_test_words = len(test_set.wordlist) 22 | if len(guesses) != num_test_words: 23 | print("Size of guesses must equal number of test words ({})!".format(num_test_words)) 24 | for word_id in range(num_test_words): 25 | if guesses[word_id] != test_set.wordlist[word_id]: 26 | S += 1 27 | 28 | print("\n**** WER = {}".format(float(S) / float(N))) 29 | print("Total correct: {} out of {}".format(N - S, N)) 30 | print('Video Recognized Correct') 31 | print('=====================================================================================================') 32 | for video_num in test_set.sentences_index: 33 | correct_sentence = [test_set.wordlist[i] for i in test_set.sentences_index[video_num]] 34 | recognized_sentence = [guesses[i] for i in test_set.sentences_index[video_num]] 35 | for i in range(len(recognized_sentence)): 36 | if recognized_sentence[i] != correct_sentence[i]: 37 | recognized_sentence[i] = '*' + recognized_sentence[i] 38 | print('{:5}: {:60} {}'.format(video_num, ' '.join(recognized_sentence), ' '.join(correct_sentence))) 39 | 40 | 41 | def getKey(item): 42 | return item[1] 43 | 44 | 45 | def train_all_words(training: WordsData, model_selector): 46 | """ train all words given a training set and selector 47 | 48 | :param training: WordsData object (training set) 49 | :param model_selector: class (subclassed from ModelSelector) 50 | :return: dict of models keyed by word 51 | """ 52 | sequences = training.get_all_sequences() 53 | Xlengths = training.get_all_Xlengths() 54 | model_dict = {} 55 | for word in training.words: 56 | model = model_selector(sequences, Xlengths, word, 57 | n_constant=3).select() 58 | model_dict[word] = model 59 | return model_dict 60 | 61 | 62 | def combine_sequences(split_index_list, sequences): 63 | ''' 64 | concatenate sequences referenced in an index list and returns tuple of the new X,lengths 65 | 66 | useful when recombining sequences split using KFold for hmmlearn 67 | 68 | :param split_index_list: a list of indices as created by KFold splitting 69 | :param sequences: list of feature sequences 70 | :return: tuple of list, list in format of X,lengths use in hmmlearn 71 | ''' 72 | sequences_fold = [sequences[idx] for idx in split_index_list] 73 | X = [item for sublist in sequences_fold for item in sublist] 74 | lengths = [len(sublist) for sublist in sequences_fold] 75 | return X, lengths 76 | 77 | 78 | def putHTML(color, msg): 79 | source = """{}
""".format(color, msg) 80 | return HTML(source) 81 | 82 | 83 | def feedback(passed, failmsg='', passmsg='Correct!'): 84 | if passed: 85 | return putHTML('green', passmsg) 86 | else: 87 | return putHTML('red', failmsg) 88 | 89 | 90 | def test_features_tryit(asl): 91 | print('asl.df sample') 92 | display(asl.df.head()) 93 | sample = asl.df.ix[98, 1][GROUND_FEATURES].tolist() 94 | correct = [9, 113, -12, 119] 95 | failmsg = 'The values returned were not correct. Expected: {} Found: {}'.format(correct, sample) 96 | return feedback(sample == correct, failmsg) 97 | 98 | 99 | def test_std_tryit(df_std): 100 | print('df_std') 101 | display(df_std) 102 | sample = df_std.ix['man-1'][RAW_FEATURES] 103 | correct = [15.154425, 36.328485, 18.901917, 54.902340] 104 | failmsg = 'The raw man-1 values returned were not correct.\nExpected: {} for {}'.format(correct, RAW_FEATURES) 105 | return feedback(np.allclose(sample, correct, .001), failmsg) 106 | -------------------------------------------------------------------------------- /recognizer/data/test_words.csv: -------------------------------------------------------------------------------- 1 | video,speaker,word,startframe,endframe 2 | 2,woman-1,JOHN,7,20 3 | 2,woman-1,WRITE,23,36 4 | 2,woman-1,HOMEWORK,38,63 5 | 7,man-1,JOHN,22,39 6 | 7,man-1,CAN,42,47 7 | 7,man-1,GO,48,56 8 | 7,man-1,CAN,62,73 9 | 12,woman-2,JOHN,9,15 10 | 12,woman-2,CAN,19,24 11 | 12,woman-2,GO,25,34 12 | 12,woman-2,CAN,35,51 13 | 21,woman-2,JOHN,6,26 14 | 21,woman-2,FISH,33,50 15 | 21,woman-2,WONT,53,60 16 | 21,woman-2,EAT,64,74 17 | 21,woman-2,BUT,78,85 18 | 21,woman-2,CAN,85,90 19 | 21,woman-2,EAT,92,97 20 | 21,woman-2,CHICKEN,99,109 21 | 25,woman-2,JOHN,33,41 22 | 25,woman-2,LIKE,43,48 23 | 25,woman-2,IX,49,62 24 | 25,woman-2,IX,62,68 25 | 25,woman-2,IX,68,79 26 | 28,woman-2,JOHN,5,12 27 | 28,woman-2,LIKE,13,19 28 | 28,woman-2,IX,20,30 29 | 28,woman-2,IX,30,38 30 | 28,woman-2,IX,38,49 31 | 30,man-1,JOHN,9,17 32 | 30,man-1,LIKE,19,23 33 | 30,man-1,IX,24,31 34 | 30,man-1,IX,32,38 35 | 30,man-1,IX,39,45 36 | 36,man-1,MARY,13,32 37 | 36,man-1,VEGETABLE,47,66 38 | 36,man-1,KNOW,68,77 39 | 36,man-1,IX,78,83 40 | 36,man-1,LIKE,85,89 41 | 36,man-1,CORN1,92,112 42 | 40,man-1,JOHN,14,37 43 | 40,man-1,IX,37,48 44 | 40,man-1,THINK,51,58 45 | 40,man-1,MARY,61,75 46 | 40,man-1,LOVE,78,87 47 | 43,woman-1,JOHN,3,10 48 | 43,woman-1,MUST,12,19 49 | 43,woman-1,BUY,22,27 50 | 43,woman-1,HOUSE,30,45 51 | 50,woman-1,FUTURE,13,20 52 | 50,woman-1,JOHN,21,26 53 | 50,woman-1,BUY,29,34 54 | 50,woman-1,CAR,35,41 55 | 50,woman-1,SHOULD,42,57 56 | 54,woman-2,JOHN,4,9 57 | 54,woman-2,SHOULD,10,16 58 | 54,woman-2,NOT,19,26 59 | 54,woman-2,BUY,29,33 60 | 54,woman-2,HOUSE,35,46 61 | 57,man-1,JOHN,0,14 62 | 57,man-1,DECIDE,17,29 63 | 57,man-1,VISIT,29,38 64 | 57,man-1,MARY,39,48 65 | 67,woman-1,JOHN,5,9 66 | 67,woman-1,FUTURE,12,30 67 | 67,woman-1,NOT,33,40 68 | 67,woman-1,BUY,43,49 69 | 67,woman-1,HOUSE,50,64 70 | 71,woman-2,JOHN,3,9 71 | 71,woman-2,WILL,9,17 72 | 71,woman-2,VISIT,17,28 73 | 71,woman-2,MARY,28,40 74 | 74,man-1,JOHN,4,15 75 | 74,man-1,NOT,15,23 76 | 74,man-1,VISIT,25,37 77 | 74,man-1,MARY,39,47 78 | 77,woman-2,ANN,6,17 79 | 77,woman-2,BLAME,17,28 80 | 77,woman-2,MARY,28,47 81 | 84,woman-1,IX-1P,3,6 82 | 84,woman-1,FIND,6,17 83 | 84,woman-1,SOMETHING-ONE,17,29 84 | 84,woman-1,BOOK,33,39 85 | 89,woman-2,JOHN,0,11 86 | 89,woman-2,IX,12,24 87 | 89,woman-2,GIVE,25,38 88 | 89,woman-2,MAN,38,55 89 | 89,woman-2,IX,55,70 90 | 89,woman-2,NEW,82,91 91 | 89,woman-2,COAT,92,103 92 | 90,man-1,JOHN,14,41 93 | 90,man-1,GIVE,44,58 94 | 90,man-1,IX,71,83 95 | 90,man-1,SOMETHING-ONE,83,97 96 | 90,man-1,WOMAN,97,111 97 | 90,man-1,BOOK,116,128 98 | 92,woman-2,JOHN,8,20 99 | 92,woman-2,GIVE,20,40 100 | 92,woman-2,IX,46,72 101 | 92,woman-2,SOMETHING-ONE,70,82 102 | 92,woman-2,WOMAN,83,92 103 | 92,woman-2,BOOK,94,101 104 | 100,woman-1,POSS,22,30 105 | 100,woman-1,NEW,37,47 106 | 100,woman-1,CAR,47,54 107 | 100,woman-1,BREAK-DOWN,56,67 108 | 105,woman-1,JOHN,21,32 109 | 105,woman-1,LEG,35,42 110 | 107,man-1,JOHN,22,38 111 | 107,man-1,POSS,42,49 112 | 107,man-1,FRIEND,50,63 113 | 107,man-1,HAVE,66,72 114 | 107,man-1,CANDY,73,86 115 | 108,woman-2,WOMAN,29,40 116 | 108,woman-2,ARRIVE,43,53 117 | 113,man-1,IX,14,21 118 | 113,man-1,CAR,23,30 119 | 113,man-1,BLUE,31,40 120 | 113,man-1,SUE,40,50 121 | 113,man-1,BUY,52,65 122 | 119,man-1,SUE,20,33 123 | 119,man-1,BUY,35,42 124 | 119,man-1,IX,42,51 125 | 119,man-1,CAR,52,60 126 | 119,man-1,BLUE,62,69 127 | 122,woman-2,JOHN,6,15 128 | 122,woman-2,READ,17,27 129 | 122,woman-2,BOOK,29,35 130 | 139,man-1,JOHN,14,22 131 | 139,man-1,BUY,25,29 132 | 139,man-1,WHAT,31,38 133 | 139,man-1,YESTERDAY,43,54 134 | 139,man-1,BOOK,62,74 135 | 142,woman-1,JOHN,4,13 136 | 142,woman-1,BUY,17,24 137 | 142,woman-1,YESTERDAY,27,36 138 | 142,woman-1,WHAT,40,53 139 | 142,woman-1,BOOK,60,71 140 | 158,man-1,LOVE,11,18 141 | 158,man-1,JOHN,20,30 142 | 158,man-1,WHO,32,44 143 | 167,man-1,JOHN,10,24 144 | 167,man-1,IX,27,33 145 | 167,man-1,SAY,35,40 146 | 167,man-1,LOVE,40,54 147 | 167,man-1,MARY,57,67 148 | 171,man-1,JOHN,16,28 149 | 171,man-1,MARY,35,45 150 | 171,man-1,BLAME,48,56 151 | 174,man-1,PEOPLE,7,20 152 | 174,man-1,GROUP,22,35 153 | 174,man-1,GIVE1,40,69 154 | 174,man-1,JANA,73,92 155 | 174,man-1,TOY,95,104 156 | 181,man-1,JOHN,16,45 157 | 181,man-1,ARRIVE,49,58 158 | 184,man-1,ALL,13,24 159 | 184,man-1,BOY,26,36 160 | 184,man-1,GIVE,40,68 161 | 184,man-1,TEACHER,72,81 162 | 184,man-1,APPLE,85,96 163 | 189,man-1,JOHN,16,30 164 | 189,man-1,GIVE,31,40 165 | 189,man-1,GIRL,42,50 166 | 189,man-1,BOX,52,64 167 | 193,man-1,JOHN,10,25 168 | 193,man-1,GIVE,27,34 169 | 193,man-1,GIRL,36,42 170 | 193,man-1,BOX,46,60 171 | 199,woman-1,LIKE,15,23 172 | 199,woman-1,CHOCOLATE,24,32 173 | 199,woman-1,WHO,36,60 174 | 201,woman-2,JOHN,6,14 175 | 201,woman-2,TELL,17,21 176 | 201,woman-2,MARY,21,28 177 | 201,woman-2,IX-1P,31,41 178 | 201,woman-2,BUY,41,46 179 | 201,woman-2,HOUSE,48,64 180 | -------------------------------------------------------------------------------- /planning/example_have_cake.py: -------------------------------------------------------------------------------- 1 | from aimacode.logic import PropKB 2 | from aimacode.planning import Action 3 | from aimacode.search import ( 4 | Node, breadth_first_search, astar_search, depth_first_graph_search, 5 | uniform_cost_search, greedy_best_first_graph_search, Problem, 6 | ) 7 | from aimacode.utils import expr 8 | from lp_utils import ( 9 | FluentState, encode_state, decode_state 10 | ) 11 | from my_planning_graph import PlanningGraph 12 | from run_search import run_search 13 | 14 | from functools import lru_cache 15 | 16 | 17 | class HaveCakeProblem(Problem): 18 | def __init__(self, initial: FluentState, goal: list): 19 | self.state_map = initial.pos + initial.neg 20 | Problem.__init__(self, encode_state(initial, self.state_map), goal=goal) 21 | self.actions_list = self.get_actions() 22 | 23 | def get_actions(self): 24 | precond_pos = [expr("Have(Cake)")] 25 | precond_neg = [] 26 | effect_add = [expr("Eaten(Cake)")] 27 | effect_rem = [expr("Have(Cake)")] 28 | eat_action = Action(expr("Eat(Cake)"), 29 | [precond_pos, precond_neg], 30 | [effect_add, effect_rem]) 31 | precond_pos = [] 32 | precond_neg = [expr("Have(Cake)")] 33 | effect_add = [expr("Have(Cake)")] 34 | effect_rem = [] 35 | bake_action = Action(expr("Bake(Cake)"), 36 | [precond_pos, precond_neg], 37 | [effect_add, effect_rem]) 38 | return [eat_action, bake_action] 39 | 40 | def actions(self, state: str) -> list: # of Action 41 | possible_actions = [] 42 | kb = PropKB() 43 | kb.tell(decode_state(state, self.state_map).pos_sentence()) 44 | for action in self.actions_list: 45 | is_possible = True 46 | for clause in action.precond_pos: 47 | if clause not in kb.clauses: 48 | is_possible = False 49 | for clause in action.precond_neg: 50 | if clause in kb.clauses: 51 | is_possible = False 52 | if is_possible: 53 | possible_actions.append(action) 54 | return possible_actions 55 | 56 | def result(self, state: str, action: Action): 57 | new_state = FluentState([], []) 58 | old_state = decode_state(state, self.state_map) 59 | for fluent in old_state.pos: 60 | if fluent not in action.effect_rem: 61 | new_state.pos.append(fluent) 62 | for fluent in action.effect_add: 63 | if fluent not in new_state.pos: 64 | new_state.pos.append(fluent) 65 | for fluent in old_state.neg: 66 | if fluent not in action.effect_add: 67 | new_state.neg.append(fluent) 68 | for fluent in action.effect_rem: 69 | if fluent not in new_state.neg: 70 | new_state.neg.append(fluent) 71 | return encode_state(new_state, self.state_map) 72 | 73 | def goal_test(self, state: str) -> bool: 74 | kb = PropKB() 75 | kb.tell(decode_state(state, self.state_map).pos_sentence()) 76 | for clause in self.goal: 77 | if clause not in kb.clauses: 78 | return False 79 | return True 80 | 81 | def h_1(self, node: Node): 82 | # note that this is not a true heuristic 83 | h_const = 1 84 | return h_const 85 | 86 | @lru_cache(maxsize=8192) 87 | def h_pg_levelsum(self, node: Node): 88 | # uses the planning graph level-sum heuristic calculated 89 | # from this node to the goal 90 | # requires implementation in PlanningGraph 91 | pg = PlanningGraph(self, node.state) 92 | pg_levelsum = pg.h_levelsum() 93 | return pg_levelsum 94 | 95 | @lru_cache(maxsize=8192) 96 | def h_ignore_preconditions(self, node: Node): 97 | # not implemented 98 | count = 0 99 | return count 100 | 101 | 102 | def have_cake(): 103 | def get_init(): 104 | pos = [expr('Have(Cake)'), 105 | ] 106 | neg = [expr('Eaten(Cake)'), 107 | ] 108 | return FluentState(pos, neg) 109 | 110 | def get_goal(): 111 | return [expr('Have(Cake)'), 112 | expr('Eaten(Cake)'), 113 | ] 114 | 115 | return HaveCakeProblem(get_init(), get_goal()) 116 | 117 | 118 | if __name__ == '__main__': 119 | p = have_cake() 120 | print("**** Have Cake example problem setup ****") 121 | print("Initial state for this problem is {}".format(p.initial)) 122 | print("Actions for this domain are:") 123 | for a in p.actions_list: 124 | print(' {}{}'.format(a.name, a.args)) 125 | print("Fluents in this problem are:") 126 | for f in p.state_map: 127 | print(' {}'.format(f)) 128 | print("Goal requirement for this problem are:") 129 | for g in p.goal: 130 | print(' {}'.format(g)) 131 | print() 132 | print("*** Breadth First Search") 133 | run_search(p, breadth_first_search) 134 | print("*** Depth First Search") 135 | run_search(p, depth_first_graph_search) 136 | print("*** Uniform Cost Search") 137 | run_search(p, uniform_cost_search) 138 | print("*** Greedy Best First Graph Search - null heuristic") 139 | run_search(p, greedy_best_first_graph_search, parameter=p.h_1) 140 | print("*** A-star null heuristic") 141 | run_search(p, astar_search, p.h_1) 142 | # print("A-star ignore preconditions heuristic") 143 | # rs(p, "astar_search - ignore preconditions heuristic", astar_search, p.h_ignore_preconditions) 144 | # print(""A-star levelsum heuristic) 145 | # rs(p, "astar_search - levelsum heuristic", astar_search, p.h_pg_levelsum) 146 | -------------------------------------------------------------------------------- /planning/run_search.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from timeit import default_timer as timer 3 | from aimacode.search import InstrumentedProblem 4 | from aimacode.search import (breadth_first_search, astar_search, 5 | breadth_first_tree_search, depth_first_graph_search, uniform_cost_search, 6 | greedy_best_first_graph_search, depth_limited_search, 7 | recursive_best_first_search) 8 | from my_air_cargo_problems import air_cargo_p1, air_cargo_p2, air_cargo_p3 9 | 10 | PROBLEM_CHOICE_MSG = """ 11 | Select from the following list of air cargo problems. You may choose more than 12 | one by entering multiple selections separated by spaces. 13 | """ 14 | 15 | SEARCH_METHOD_CHOICE_MSG = """ 16 | Select from the following list of search functions. You may choose more than 17 | one by entering multiple selections separated by spaces. 18 | """ 19 | 20 | INVALID_ARG_MSG = """ 21 | You must either use the -m flag to run in manual mode, or use both the -p and 22 | -s flags to specify a list of problems and search algorithms to run. Valid 23 | choices for each include: 24 | """ 25 | 26 | PROBLEMS = [["Air Cargo Problem 1", air_cargo_p1], 27 | ["Air Cargo Problem 2", air_cargo_p2], 28 | ["Air Cargo Problem 3", air_cargo_p3]] 29 | SEARCHES = [["breadth_first_search", breadth_first_search, ""], 30 | ['breadth_first_tree_search', breadth_first_tree_search, ""], 31 | ['depth_first_graph_search', depth_first_graph_search, ""], 32 | ['depth_limited_search', depth_limited_search, ""], 33 | ['uniform_cost_search', uniform_cost_search, ""], 34 | ['recursive_best_first_search', recursive_best_first_search, 'h_1'], 35 | ['greedy_best_first_graph_search', greedy_best_first_graph_search, 'h_1'], 36 | ['astar_search', astar_search, 'h_1'], 37 | ['astar_search', astar_search, 'h_ignore_preconditions'], 38 | ['astar_search', astar_search, 'h_pg_levelsum'], 39 | ] 40 | 41 | 42 | class PrintableProblem(InstrumentedProblem): 43 | """ InstrumentedProblem keeps track of stats during search, and this 44 | class modifies the print output of those statistics for air cargo 45 | problems. 46 | """ 47 | 48 | def __repr__(self): 49 | return '{:^10d} {:^10d} {:^10d}'.format(self.succs, self.goal_tests, self.states) 50 | 51 | 52 | def run_search(problem, search_function, parameter=None): 53 | 54 | start = timer() 55 | ip = PrintableProblem(problem) 56 | if parameter is not None: 57 | node = search_function(ip, parameter) 58 | else: 59 | node = search_function(ip) 60 | end = timer() 61 | print("\nExpansions Goal Tests New Nodes") 62 | print("{}\n".format(ip)) 63 | show_solution(node, end - start) 64 | print() 65 | 66 | 67 | def manual(): 68 | 69 | print(PROBLEM_CHOICE_MSG) 70 | for idx, (name, _) in enumerate(PROBLEMS): 71 | print(" {!s}. {}".format(idx+1, name)) 72 | p_choices = input("> ").split() 73 | 74 | print(SEARCH_METHOD_CHOICE_MSG) 75 | for idx, (name, _, heuristic) in enumerate(SEARCHES): 76 | print(" {!s}. {} {}".format(idx+1, name, heuristic)) 77 | s_choices = input("> ").split() 78 | 79 | main(p_choices, s_choices) 80 | 81 | print("\nYou can run this selection again automatically from the command " + 82 | "line\nwith the following command:") 83 | print("\n python {} -p {} -s {}\n".format(__file__, 84 | " ".join(p_choices), 85 | " ".join(s_choices))) 86 | 87 | 88 | def main(p_choices, s_choices): 89 | 90 | problems = [PROBLEMS[i-1] for i in map(int, p_choices)] 91 | searches = [SEARCHES[i-1] for i in map(int, s_choices)] 92 | 93 | for pname, p in problems: 94 | 95 | for sname, s, h in searches: 96 | hstring = h if not h else " with {}".format(h) 97 | print("\nSolving {} using {}{}...".format(pname, sname, hstring)) 98 | 99 | _p = p() 100 | _h = None if not h else getattr(_p, h) 101 | run_search(_p, s, _h) 102 | 103 | 104 | def show_solution(node, elapsed_time): 105 | print("Plan length: {} Time elapsed in seconds: {}".format(len(node.solution()), elapsed_time)) 106 | for action in node.solution(): 107 | print("{}{}".format(action.name, action.args)) 108 | 109 | if __name__=="__main__": 110 | parser = argparse.ArgumentParser(description="Solve air cargo planning problems " + 111 | "using a variety of state space search methods including uninformed, greedy, " + 112 | "and informed heuristic search.") 113 | parser.add_argument('-m', '--manual', action="store_true", 114 | help="Interactively select the problems and searches to run.") 115 | parser.add_argument('-p', '--problems', nargs="+", choices=range(1, len(PROBLEMS)+1), type=int, metavar='', 116 | help="Specify the indices of the problems to solve as a list of space separated values. Choose from: {!s}".format(list(range(1, len(PROBLEMS)+1)))) 117 | parser.add_argument('-s', '--searches', nargs="+", choices=range(1, len(SEARCHES)+1), type=int, metavar='', 118 | help="Specify the indices of the search algorithms to use as a list of space separated values. Choose from: {!s}".format(list(range(1, len(SEARCHES)+1)))) 119 | args = parser.parse_args() 120 | 121 | if args.manual: 122 | manual() 123 | elif args.problems and args.searches: 124 | main(list(sorted(set(args.problems))), list(sorted(set((args.searches))))) 125 | else: 126 | print() 127 | parser.print_help() 128 | print(INVALID_ARG_MSG) 129 | print("Problems\n-----------------") 130 | for idx, (name, _) in enumerate(PROBLEMS): 131 | print(" {!s}. {}".format(idx+1, name)) 132 | print() 133 | print("Search Algorithms\n-----------------") 134 | for idx, (name, _, heuristic) in enumerate(SEARCHES): 135 | print(" {!s}. {} {}".format(idx+1, name, heuristic)) 136 | print() 137 | print("Use manual mode for interactive selection:\n\n\tpython run_search.py -m\n") 138 | -------------------------------------------------------------------------------- /recognizer/my_model_selectors.py: -------------------------------------------------------------------------------- 1 | import math 2 | import statistics 3 | import warnings 4 | 5 | import numpy as np 6 | from hmmlearn.hmm import GaussianHMM 7 | from sklearn.model_selection import KFold 8 | from asl_utils import combine_sequences 9 | 10 | 11 | class ModelSelector(object): 12 | ''' 13 | base class for model selection (strategy design pattern) 14 | ''' 15 | 16 | def __init__(self, all_word_sequences: dict, all_word_Xlengths: dict, this_word: str, 17 | n_constant=3, 18 | min_n_components=2, max_n_components=10, 19 | random_state=14, verbose=False): 20 | self.words = all_word_sequences 21 | self.hwords = all_word_Xlengths 22 | self.sequences = all_word_sequences[this_word] 23 | self.X, self.lengths = all_word_Xlengths[this_word] 24 | self.this_word = this_word 25 | self.n_constant = n_constant 26 | self.min_n_components = min_n_components 27 | self.max_n_components = max_n_components 28 | self.random_state = random_state 29 | self.verbose = verbose 30 | 31 | 32 | def select(self): 33 | raise NotImplementedError 34 | 35 | def base_model(self, num_states): 36 | # with warnings.catch_warnings(): 37 | warnings.filterwarnings("ignore", category=DeprecationWarning) 38 | # warnings.filterwarnings("ignore", category=RuntimeWarning) 39 | try: 40 | hmm_model = GaussianHMM(n_components=num_states, covariance_type="diag", n_iter=1000, 41 | random_state=self.random_state, verbose=False).fit(self.X, self.lengths) 42 | if self.verbose: 43 | print("model created for {} with {} states".format(self.this_word, num_states)) 44 | return hmm_model 45 | except: 46 | if self.verbose: 47 | print("failure on {} with {} states".format(self.this_word, num_states)) 48 | return None 49 | 50 | 51 | class SelectorConstant(ModelSelector): 52 | """ select the model with value self.n_constant 53 | 54 | """ 55 | 56 | def select(self): 57 | """ select based on n_constant value 58 | 59 | :return: GaussianHMM object 60 | """ 61 | best_num_components = self.n_constant 62 | return self.base_model(best_num_components) 63 | 64 | 65 | class SelectorBIC(ModelSelector): 66 | """ select the model with the lowest Bayesian Information Criterion(BIC) score 67 | 68 | http://www2.imm.dtu.dk/courses/02433/doc/ch6_slides.pdf 69 | Bayesian information criteria: BIC = -2 * logL + p * logN 70 | """ 71 | def get_bic_score(self, n_component): 72 | model = self.base_model(n_component) 73 | logL = model.score(self.X, self.lengths) 74 | logN = np.log(len(self.X)) 75 | d = model.n_features 76 | # p = = n^2 + 2*d*n - 1 77 | p = n_component ** 2 + 2 * d * n_component - 1 78 | bic_score = -2.0 * logL + p * logN 79 | return model, bic_score 80 | 81 | def select(self): 82 | """ select the best model for self.this_word based on 83 | BIC score for n between self.min_n_components and self.max_n_components 84 | 85 | :return: GaussianHMM object 86 | """ 87 | warnings.filterwarnings("ignore", category=DeprecationWarning) 88 | best_score = float("-Inf") 89 | best_model = None 90 | try: 91 | for n_component in range(self.min_n_components, self.max_n_components + 1): 92 | model, bic_score = self.get_bic_score(n_component) 93 | if bic_score > best_score: 94 | best_model, best_score = model, bic_score 95 | return best_model 96 | except Exception as e: 97 | pass 98 | return self.base_model(self.n_constant) 99 | 100 | class SelectorDIC(ModelSelector): 101 | ''' select best model based on Discriminative Information Criterion 102 | Biem, Alain. "A model selection criterion for classification: Application to hmm topology optimization." 103 | Document Analysis and Recognition, 2003. Proceedings. Seventh International Conference on. IEEE, 2003. 104 | http://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.58.6208&rep=rep1&type=pdf 105 | DIC = log(P(X(i)) - 1/(M-1)SUM(log(P(X(all but i)) 106 | ''' 107 | def get_dic_score(self, n_component): 108 | model = self.base_model(n_component) 109 | right_part_scores = [] 110 | for w, (X, lengths) in self.hwords.items(): 111 | if w != self.this_word: 112 | right_part_scores.append(model.score(X, lengths)) 113 | dic_score = model.score(self.X, self.lengths) - np.mean(right_part_scores) 114 | return model, dic_score 115 | 116 | def select(self): 117 | warnings.filterwarnings("ignore", category=DeprecationWarning) 118 | best_score = float("-Inf") 119 | best_model = None 120 | try: 121 | for n_component in range(self.min_n_components, self.max_n_components + 1): 122 | model, dic_score = self.get_dic_score(n_component) 123 | if dic_score > best_score: 124 | best_model, best_score = model, dic_score 125 | return best_model 126 | except Exception as e: 127 | pass 128 | return self.base_model(self.n_constant) 129 | 130 | 131 | class SelectorCV(ModelSelector): 132 | ''' select best model based on average log Likelihood of cross-validation folds 133 | 134 | ''' 135 | def get_logL(self, num_hidden_states, split_method): 136 | model = self.base_model(num_hidden_states) 137 | 138 | for train_idx, test_idx in split_method.split(self.sequences): 139 | # Get test sequences 140 | test_X, test_lengths = combine_sequences(test_idx, self.sequences) 141 | logL = model.score(test_X, test_lengths) 142 | return model, np.mean(logL) 143 | 144 | def select(self): 145 | warnings.filterwarnings("ignore", category=DeprecationWarning) 146 | best_score = float("-Inf") 147 | best_model = None 148 | split_method = KFold() 149 | try: 150 | for n_component in range(self.min_n_components, self.max_n_components + 1): 151 | model, mean_logL = self.get_logL(n_component, split_method) 152 | if mean_logL > best_score: 153 | best_score = mean_logL 154 | best_model = model 155 | return best_model 156 | except Exception as e: 157 | pass 158 | return self.base_model(self.n_constant) 159 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /planning/heuristic_analysis.md: -------------------------------------------------------------------------------- 1 | # Planning Search Comparison Analysis 2 | ### Ruihan Zou 3 | This report is the comparison among planning searches on the three planning problems. 4 | The following tables are showing the details of planning searches performance on each problem. 5 | 6 | |Search Type | Expansions | Goal Tests | New Nodes | Plan Length | Time elapsed | Optimality | 7 | |:-|:--|:--|:--|:--|:--|:--| 8 | |Breadth First Search| 43 | 56 | 180 | 6 | 0.0319 | Yes | 9 | |Breadth First Tree Search | 1458 | 1459 | 5960 | 6 | 0.961 | Yes | 10 | | Depth First Graph Search | 21 | 22 | 84 | 20 | 0.015 | No | 11 | | Depth Limited Search | 101 | 271 | 414 | 50 | 0.0935 | No | 12 | | Uniform Cost Search | 55 | 57 | 224 | 6 | 0.0375 | Yes | 13 | | Recursive Best First Search H1 | 4229 | 4230 | 17023 | 6 | 2.824 | Yes | 14 | | Greedy Best First Search H1 | 7 | 9 | 28 | 6 | 0.0053 | Yes | 15 | | Astar Search with h_1 | 55 | 57 | 224 | 6 | 0.0403 | Yes | 16 | | Astar Search with h_ignore_preconditions | 41 | 43 | 170 | 6 | 0.0389 | Yes | 17 | | Astar Search with h_pg_levelsum | 55 | 57 | 224 | 6 | 1.724 | Yes | 18 | [Air Cargo Problem 1] 19 | 20 | The optimal plan for problem one is found by Breadth First Search, Breadth First Tree Search, Uniform Cost Search, Recursive Best First Search, Greedy Best First Graph Search, A* Search with a heuristic. The priority on choosing the best planning search is following this rule: 21 | plan length > Time elapsed > Expansions and the one has smaller value is the better one. 22 | 23 | - Load(C1, P1, SFO) 24 | - Load(C2, P2, JFK) 25 | - Fly(P2, JFK, SFO) 26 | - Unload(C2, P2, SFO) 27 | - Fly(P1, SFO, JFK) 28 | - Unload(C1, P1, JFK) 29 | 30 | Based on the above rule, the Greedy Best First Search H1 is the best planning search solution for problem1. The greedy search tries to expand the node which distance is closet to the goal. Here we use the straight-line distance heuristic. In problem 1, the goal is straight forward, and there is no any obstacle between the destination. In this case, the greedy algorithm can guarantee the best solution would be found. 31 | The Breath First Graph Search is also doing well as the goal is at a shallow depth, which means it is a limited depth. However, it is not guaranteed that Breath First Search can give an optimal solution if it the actions cost is decreasing. 32 | The Depth-First Graph Search reaches the goal in short time, but the path length is not optimal. Here is because the property of Depth First Graph Search, which expanded the deepest node in the current frontier of the search tree. So once it reaches the goal, the search would stop though there is an optimal one from other nodes. 33 | The performance of Astar search depends on the heuristic function, and the optimal solution is guaranteed. 34 | 35 | |Search Type | Expansions | Goal Tests | New Nodes | Plan Length | Time elapsed | Optimality | 36 | |:-|:--|:--|:--|:--|:--|:--| 37 | |Breadth First Search| 3401 | 4672 | 31049 | 9 | 15.016 | Yes | 38 | |Breadth First Tree Search | - | - | - | - | - | - | 39 | | Depth First Graph Search | 1192 | 1193 | 10606 | 1138 | 9.287 | No | 40 | | Depth Limited Search | - | - | - | - | - | No | 41 | | Uniform Cost Search | 4761 | 4763 | 43206 | 9 | 12.377 | Yes | 42 | | Recursive Best First Search H1 | - | - | - | - | - | No | 43 | | Greedy Best First Search H1 | 550 | 552 | 4950 | 9 | 1.424 | Yes | 44 | | Astar Search with h_1 | 4761 | 4763 | 43206 | 9 | 11.984 | Yes | 45 | | Astar Search with h_ignore_preconditions | 1450 | 1452 | 13303 | 9 | 4.415 | Yes | 46 | | Astar Search with h_pg_levelsum | - | - | - | - | - | No | 47 | [Air Cargo Problem 2] 48 | 49 | Breadth First Tree Search, Depth-Limited Search, Recursive Best First Search H1 and Astar Search h_pg_levelsum exceeded the time limit. 50 | 51 | The optimal solution is: 52 | - Load(C1, P1, SFO) 53 | - Load(C2, P2, JFK) 54 | - Load(C3, P3, ATL) 55 | - Fly(P1, SFO, JFK) 56 | - Fly(P2, JFK, SFO) 57 | - Fly(P3, ATL, SFO) 58 | - Unload(C3, P3, SFO) 59 | - Unload(C2, P2, SFO) 60 | - Unload(C1, P1, JFK) 61 | 62 | In problem 2, the Greedy Best First Search is still the best solution. The second choice is Astar Search with h_ignore_preconditions. The Breath First Search took much longer time(15s > 0.0319) comparing problem 1. Because the depth and branch of search graph has increased and the time complexity is BFS takes O(b^(d + 1)) time and memory, where b is the "branching factor" of the graph (the average out-degree) 63 | 64 | |Search Type | Expansions | Goal Tests | New Nodes | Plan Length | Time elapsed | Optimality | 65 | |:-|:--|:--|:--|:--|:--|:--| 66 | |Breadth First Search| 14491 | 17947 | 128184 | 12 | 111.088 | Yes | 67 | |Breadth First Tree Search | - | - | - | - | - | - | 68 | | Depth First Graph Search | 2099 | 2100 | 17558 | 2014 | 24.345 | No | 69 | | Depth Limited Search | - | - | - | - | - | No | 70 | | Uniform Cost Search | 17783 | 17785 | 155920 | 12 | 52.357 | Yes | 71 | | Recursive Best First Search H1 | - | - | - | - | - | No | 72 | | Greedy Best First Search H1 | 4031 | 4033 | 35794 | 22 | 11.924 | No | 73 | | Astar Search with h_1 | 17783 | 17785 | 155920 | 12 | 53.047 | Yes | 74 | | Astar Search with h_ignore_preconditions | 5003 | 5005 | 44586 | 12 | 17.453 | Yes | 75 | | Astar Search with h_pg_levelsum | - | - | - | - | - | No | 76 | [Air Cargo Problem 3] 77 | 78 | From Problem 3, the Astar search with h_ignore_predictions is the best solution. The ignore preconditions relaxes all preconditions from actions. In this air cargo problem 3, no matter C1 in which airport, it can reach to JFK directly At any plane which will land at JFK. The below is the optimal solution: 79 | 80 | - Load(C1, P1, SFO) 81 | - Load(C2, P2, JFK) 82 | - Fly(P1, SFO, ATL) 83 | - Load(C3, P1, ATL) 84 | - Fly(P2, JFK, ORD) 85 | - Load(C4, P2, ORD) 86 | - Fly(P1, ATL, JFK) 87 | - Fly(P2, ORD, SFO) 88 | - Unload(C4, P2, SFO) 89 | - Unload(C3, P1, JFK) 90 | - Unload(C2, P2, SFO) 91 | - Unload(C1, P1, JFK) 92 | 93 | Greedy Best First Search H1 fails on giving the optimal solution. The reason is there are airports in between the route of an initial airport and destination airport. The Best first search is taking much longer time than in problem 1 and 2 when cargos(branch factor) and airports are increasing(depth factor). 94 | 95 | In conclusion, we choose Breadth First Search, or Greedy Best First Search when the problem has a swallow goal. But one of the advantages of Breadth First Search is it can guarantee an optimal solution. If we have memory limit and we just want to know whether a solution of reaching to the goal exist for the problem, we choose Depth First Search. Otherwise, the Astar search with heuristic can be considered when the problem is complex which means the search space is huge. 96 | 97 | 98 | 99 | 100 | #Reference 101 | Russell, S. J., & Norvig, P. (2016). Artificial intelligence: a modern approach (3rd ed.). Boston: Pearson. 102 | -------------------------------------------------------------------------------- /planning/tests/test_my_planning_graph.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | 4 | parent = os.path.dirname(os.path.realpath(__file__)) 5 | sys.path.append(os.path.join(os.path.dirname(parent), "aimacode")) 6 | import unittest 7 | from aimacode.utils import expr 8 | from aimacode.planning import Action 9 | from example_have_cake import have_cake 10 | from my_planning_graph import ( 11 | PlanningGraph, PgNode_a, PgNode_s, mutexify 12 | ) 13 | 14 | 15 | class TestPlanningGraphLevels(unittest.TestCase): 16 | def setUp(self): 17 | self.p = have_cake() 18 | self.pg = PlanningGraph(self.p, self.p.initial) 19 | 20 | def test_add_action_level(self): 21 | # for level, nodeset in enumerate(self.pg.a_levels): 22 | # for node in nodeset: 23 | # print("Level {}: {}{})".format(level, node.action.name, node.action.args)) 24 | self.assertEqual(len(self.pg.a_levels[0]), 3, len(self.pg.a_levels[0])) 25 | self.assertEqual(len(self.pg.a_levels[1]), 6, len(self.pg.a_levels[1])) 26 | 27 | def test_add_literal_level(self): 28 | # for level, nodeset in enumerate(self.pg.s_levels): 29 | # for node in nodeset: 30 | # print("Level {}: {})".format(level, node.literal)) 31 | self.assertEqual(len(self.pg.s_levels[0]), 2, len(self.pg.s_levels[0])) 32 | self.assertEqual(len(self.pg.s_levels[1]), 4, len(self.pg.s_levels[1])) 33 | self.assertEqual(len(self.pg.s_levels[2]), 4, len(self.pg.s_levels[2])) 34 | 35 | 36 | class TestPlanningGraphMutex(unittest.TestCase): 37 | def setUp(self): 38 | self.p = have_cake() 39 | self.pg = PlanningGraph(self.p, self.p.initial) 40 | # some independent nodes for testing mutex 41 | self.na1 = PgNode_a(Action(expr('Go(here)'), 42 | [[], []], [[expr('At(here)')], []])) 43 | self.na2 = PgNode_a(Action(expr('Go(there)'), 44 | [[], []], [[expr('At(there)')], []])) 45 | self.na3 = PgNode_a(Action(expr('Noop(At(there))'), 46 | [[expr('At(there)')], []], [[expr('At(there)')], []])) 47 | self.na4 = PgNode_a(Action(expr('Noop(At(here))'), 48 | [[expr('At(here)')], []], [[expr('At(here)')], []])) 49 | self.na5 = PgNode_a(Action(expr('Reverse(At(here))'), 50 | [[expr('At(here)')], []], [[], [expr('At(here)')]])) 51 | self.ns1 = PgNode_s(expr('At(here)'), True) 52 | self.ns2 = PgNode_s(expr('At(there)'), True) 53 | self.ns3 = PgNode_s(expr('At(here)'), False) 54 | self.ns4 = PgNode_s(expr('At(there)'), False) 55 | self.na1.children.add(self.ns1) 56 | self.ns1.parents.add(self.na1) 57 | self.na2.children.add(self.ns2) 58 | self.ns2.parents.add(self.na2) 59 | self.na1.parents.add(self.ns3) 60 | self.na2.parents.add(self.ns4) 61 | 62 | def test_serialize_mutex(self): 63 | self.assertTrue(PlanningGraph.serialize_actions(self.pg, self.na1, self.na2), 64 | "Two persistence action nodes not marked as mutex") 65 | self.assertFalse(PlanningGraph.serialize_actions(self.pg, self.na3, self.na4), "Two No-Ops were marked mutex") 66 | self.assertFalse(PlanningGraph.serialize_actions(self.pg, self.na1, self.na3), 67 | "No-op and persistence action incorrectly marked as mutex") 68 | 69 | def test_inconsistent_effects_mutex(self): 70 | self.assertTrue(PlanningGraph.inconsistent_effects_mutex(self.pg, self.na4, self.na5), 71 | "Canceling effects not marked as mutex") 72 | self.assertFalse(PlanningGraph.inconsistent_effects_mutex(self.pg, self.na1, self.na2), 73 | "Non-Canceling effects incorrectly marked as mutex") 74 | 75 | def test_interference_mutex(self): 76 | self.assertTrue(PlanningGraph.interference_mutex(self.pg, self.na4, self.na5), 77 | "Precondition from one node opposite of effect of other node should be mutex") 78 | self.assertTrue(PlanningGraph.interference_mutex(self.pg, self.na5, self.na4), 79 | "Precondition from one node opposite of effect of other node should be mutex") 80 | self.assertFalse(PlanningGraph.interference_mutex(self.pg, self.na1, self.na2), 81 | "Non-interfering incorrectly marked mutex") 82 | 83 | def test_competing_needs_mutex(self): 84 | self.assertFalse(PlanningGraph.competing_needs_mutex(self.pg, self.na1, self.na2), 85 | "Non-competing action nodes incorrectly marked as mutex") 86 | mutexify(self.ns3, self.ns4) 87 | self.assertTrue(PlanningGraph.competing_needs_mutex(self.pg, self.na1, self.na2), 88 | "Opposite preconditions from two action nodes not marked as mutex") 89 | 90 | def test_negation_mutex(self): 91 | self.assertTrue(PlanningGraph.negation_mutex(self.pg, self.ns1, self.ns3), 92 | "Opposite literal nodes not found to be Negation mutex") 93 | self.assertFalse(PlanningGraph.negation_mutex(self.pg, self.ns1, self.ns2), 94 | "Same literal nodes found to be Negation mutex") 95 | 96 | def test_inconsistent_support_mutex(self): 97 | self.assertFalse(PlanningGraph.inconsistent_support_mutex(self.pg, self.ns1, self.ns2), 98 | "Independent node paths should NOT be inconsistent-support mutex") 99 | mutexify(self.na1, self.na2) 100 | self.assertTrue(PlanningGraph.inconsistent_support_mutex(self.pg, self.ns1, self.ns2), 101 | "Mutex parent actions should result in inconsistent-support mutex") 102 | 103 | self.na6 = PgNode_a(Action(expr('Go(everywhere)'), 104 | [[], []], [[expr('At(here)'), expr('At(there)')], []])) 105 | self.na6.children.add(self.ns1) 106 | self.ns1.parents.add(self.na6) 107 | self.na6.children.add(self.ns2) 108 | self.ns2.parents.add(self.na6) 109 | self.na6.parents.add(self.ns3) 110 | self.na6.parents.add(self.ns4) 111 | mutexify(self.na1, self.na6) 112 | mutexify(self.na2, self.na6) 113 | self.assertFalse(PlanningGraph.inconsistent_support_mutex( 114 | self.pg, self.ns1, self.ns2), 115 | "If one parent action can achieve both states, should NOT be inconsistent-support mutex, even if parent actions are themselves mutex") 116 | 117 | 118 | class TestPlanningGraphHeuristics(unittest.TestCase): 119 | def setUp(self): 120 | self.p = have_cake() 121 | self.pg = PlanningGraph(self.p, self.p.initial) 122 | 123 | def test_levelsum(self): 124 | self.assertEqual(self.pg.h_levelsum(), 1) 125 | 126 | 127 | if __name__ == '__main__': 128 | unittest.main() 129 | -------------------------------------------------------------------------------- /isolation/tournament.py: -------------------------------------------------------------------------------- 1 | """Estimate the strength rating of a student defined heuristic by competing 2 | against fixed-depth minimax and alpha-beta search agents in a round-robin 3 | tournament. 4 | 5 | NOTE: All agents are constructed from the student CustomPlayer implementation, 6 | so any errors present in that class will affect the outcome. 7 | 8 | The student agent plays a number of "fair" matches against each test agent. 9 | The matches are fair because the board is initialized randomly for both 10 | players, and the players play each match twice -- once as the first player and 11 | once as the second player. Randomizing the openings and switching the player 12 | order corrects for imbalances due to both starting position and initiative. 13 | """ 14 | import itertools 15 | import random 16 | import warnings 17 | 18 | from collections import namedtuple 19 | 20 | from isolation import Board 21 | from sample_players import (RandomPlayer, open_move_score, 22 | improved_score, center_score) 23 | from game_agent import (MinimaxPlayer, AlphaBetaPlayer, custom_score, 24 | custom_score_2, custom_score_3) 25 | 26 | NUM_MATCHES = 5 # number of matches against each opponent 27 | TIME_LIMIT = 150 # number of milliseconds before timeout 28 | 29 | DESCRIPTION = """ 30 | This script evaluates the performance of the custom_score evaluation 31 | function against a baseline agent using alpha-beta search and iterative 32 | deepening (ID) called `AB_Improved`. The three `AB_Custom` agents use 33 | ID and alpha-beta search with the custom_score functions defined in 34 | game_agent.py. 35 | """ 36 | 37 | Agent = namedtuple("Agent", ["player", "name"]) 38 | 39 | 40 | def play_round(cpu_agent, test_agents, win_counts, num_matches): 41 | """Compare the test agents to the cpu agent in "fair" matches. 42 | 43 | "Fair" matches use random starting locations and force the agents to 44 | play as both first and second player to control for advantages resulting 45 | from choosing better opening moves or having first initiative to move. 46 | """ 47 | timeout_count = 0 48 | forfeit_count = 0 49 | for _ in range(num_matches): 50 | 51 | games = sum([[Board(cpu_agent.player, agent.player), 52 | Board(agent.player, cpu_agent.player)] 53 | for agent in test_agents], []) 54 | 55 | # initialize all games with a random move and response 56 | for _ in range(2): 57 | move = random.choice(games[0].get_legal_moves()) 58 | for game in games: 59 | game.apply_move(move) 60 | 61 | # play all games and tally the results 62 | for game in games: 63 | winner, _, termination = game.play(time_limit=TIME_LIMIT) 64 | win_counts[winner] += 1 65 | 66 | if termination == "timeout": 67 | timeout_count += 1 68 | elif winner not in test_agents and termination == "forfeit": 69 | forfeit_count += 1 70 | 71 | return timeout_count, forfeit_count 72 | 73 | 74 | def update(total_wins, wins): 75 | for player in total_wins: 76 | total_wins[player] += wins[player] 77 | return total_wins 78 | 79 | 80 | def play_matches(cpu_agents, test_agents, num_matches): 81 | """Play matches between the test agent and each cpu_agent individually. """ 82 | total_wins = {agent.player: 0 for agent in test_agents} 83 | total_timeouts = 0. 84 | total_forfeits = 0. 85 | total_matches = 2 * num_matches * len(cpu_agents) 86 | 87 | print("\n{:^9}{:^13}{:^13}{:^13}{:^13}{:^13}".format( 88 | "Match #", "Opponent", test_agents[0].name, test_agents[1].name, 89 | test_agents[2].name, test_agents[3].name)) 90 | print("{:^9}{:^13} {:^5}| {:^5} {:^5}| {:^5} {:^5}| {:^5} {:^5}| {:^5}" 91 | .format("", "", *(["Won", "Lost"] * 4))) 92 | 93 | for idx, agent in enumerate(cpu_agents): 94 | wins = {test_agents[0].player: 0, 95 | test_agents[1].player: 0, 96 | test_agents[2].player: 0, 97 | test_agents[3].player: 0, 98 | agent.player: 0} 99 | 100 | print("{!s:^9}{:^13}".format(idx + 1, agent.name), end="", flush=True) 101 | 102 | counts = play_round(agent, test_agents, wins, num_matches) 103 | total_timeouts += counts[0] 104 | total_forfeits += counts[1] 105 | total_wins = update(total_wins, wins) 106 | _total = 2 * num_matches 107 | round_totals = sum([[wins[agent.player], _total - wins[agent.player]] 108 | for agent in test_agents], []) 109 | print(" {:^5}| {:^5} {:^5}| {:^5} {:^5}| {:^5} {:^5}| {:^5}" 110 | .format(*round_totals)) 111 | 112 | print("-" * 74) 113 | print("{:^9}{:^13}{:^13}{:^13}{:^13}{:^13}\n".format( 114 | "", "Win Rate:", 115 | *["{:.1f}%".format(100 * total_wins[a.player] / total_matches) 116 | for a in test_agents] 117 | )) 118 | 119 | if total_timeouts: 120 | print(("\nThere were {} timeouts during the tournament -- make sure " + 121 | "your agent handles search timeout correctly, and consider " + 122 | "increasing the timeout margin for your agent.\n").format( 123 | total_timeouts)) 124 | if total_forfeits: 125 | print(("\nYour ID search forfeited {} games while there were still " + 126 | "legal moves available to play.\n").format(total_forfeits)) 127 | 128 | 129 | def main(): 130 | 131 | # Define two agents to compare -- these agents will play from the same 132 | # starting position against the same adversaries in the tournament 133 | test_agents = [ 134 | Agent(AlphaBetaPlayer(score_fn=improved_score), "AB_Improved"), 135 | Agent(AlphaBetaPlayer(score_fn=custom_score), "AB_Custom"), 136 | Agent(AlphaBetaPlayer(score_fn=custom_score_2), "AB_Custom_2"), 137 | Agent(AlphaBetaPlayer(score_fn=custom_score_3), "AB_Custom_3") 138 | ] 139 | 140 | # Define a collection of agents to compete against the test agents 141 | cpu_agents = [ 142 | Agent(RandomPlayer(), "Random"), 143 | Agent(MinimaxPlayer(score_fn=open_move_score), "MM_Open"), 144 | Agent(MinimaxPlayer(score_fn=center_score), "MM_Center"), 145 | Agent(MinimaxPlayer(score_fn=improved_score), "MM_Improved"), 146 | Agent(AlphaBetaPlayer(score_fn=open_move_score), "AB_Open"), 147 | Agent(AlphaBetaPlayer(score_fn=center_score), "AB_Center"), 148 | Agent(AlphaBetaPlayer(score_fn=improved_score), "AB_Improved") 149 | ] 150 | 151 | print(DESCRIPTION) 152 | print("{:^74}".format("*************************")) 153 | print("{:^74}".format("Playing Matches")) 154 | print("{:^74}".format("*************************")) 155 | play_matches(cpu_agents, test_agents, NUM_MATCHES) 156 | 157 | 158 | if __name__ == "__main__": 159 | main() 160 | -------------------------------------------------------------------------------- /isolation/isoviz/js/json3.min.js: -------------------------------------------------------------------------------- 1 | /*! JSON v3.2.4 | http://bestiejs.github.com/json3 | Copyright 2012, Kit Cambridge | http://kit.mit-license.org */ 2 | ;(function(){var e=void 0,i=!0,k=null,l={}.toString,m,n,p="function"===typeof define&&define.c,q=!p&&"object"==typeof exports&&exports;q||p?"object"==typeof JSON&&JSON?p?q=JSON:(q.stringify=JSON.stringify,q.parse=JSON.parse):p&&(q=this.JSON={}):q=this.JSON||(this.JSON={});var r,t,u,x,z,B,C,D,E,F,G,H,I,J=new Date(-3509827334573292),K,O,P;try{J=-109252==J.getUTCFullYear()&&0===J.getUTCMonth()&&1==J.getUTCDate()&&10==J.getUTCHours()&&37==J.getUTCMinutes()&&6==J.getUTCSeconds()&&708==J.getUTCMilliseconds()}catch(Q){} 3 | function R(b){var c,a,d,j=b=="json";if(j||b=="json-stringify"||b=="json-parse"){if(b=="json-stringify"||j){if(c=typeof q.stringify=="function"&&J){(d=function(){return 1}).toJSON=d;try{c=q.stringify(0)==="0"&&q.stringify(new Number)==="0"&&q.stringify(new String)=='""'&&q.stringify(l)===e&&q.stringify(e)===e&&q.stringify()===e&&q.stringify(d)==="1"&&q.stringify([d])=="[1]"&&q.stringify([e])=="[null]"&&q.stringify(k)=="null"&&q.stringify([e,l,k])=="[null,null,null]"&&q.stringify({A:[d,i,false,k,"\x00\u0008\n\u000c\r\t"]})== 4 | '{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}'&&q.stringify(k,d)==="1"&&q.stringify([1,2],k,1)=="[\n 1,\n 2\n]"&&q.stringify(new Date(-864E13))=='"-271821-04-20T00:00:00.000Z"'&&q.stringify(new Date(864E13))=='"+275760-09-13T00:00:00.000Z"'&&q.stringify(new Date(-621987552E5))=='"-000001-01-01T00:00:00.000Z"'&&q.stringify(new Date(-1))=='"1969-12-31T23:59:59.999Z"'}catch(f){c=false}}if(!j)return c}if(b=="json-parse"||j){if(typeof q.parse=="function")try{if(q.parse("0")===0&&!q.parse(false)){d= 5 | q.parse('{"A":[1,true,false,null,"\\u0000\\b\\n\\f\\r\\t"]}');if(a=d.a.length==5&&d.a[0]==1){try{a=!q.parse('"\t"')}catch(o){}if(a)try{a=q.parse("01")!=1}catch(g){}}}}catch(h){a=false}if(!j)return a}return c&&a}} 6 | if(!R("json")){J||(K=Math.floor,O=[0,31,59,90,120,151,181,212,243,273,304,334],P=function(b,c){return O[c]+365*(b-1970)+K((b-1969+(c=+(c>1)))/4)-K((b-1901+c)/100)+K((b-1601+c)/400)});if(!(m={}.hasOwnProperty))m=function(b){var c={},a;if((c.__proto__=k,c.__proto__={toString:1},c).toString!=l)m=function(a){var b=this.__proto__,a=a in(this.__proto__=k,this);this.__proto__=b;return a};else{a=c.constructor;m=function(b){var c=(this.constructor||a).prototype;return b in this&&!(b in c&&this[b]===c[b])}}c= 7 | k;return m.call(this,b)};n=function(b,c){var a=0,d,j,f;(d=function(){this.valueOf=0}).prototype.valueOf=0;j=new d;for(f in j)m.call(j,f)&&a++;d=j=k;if(a)a=a==2?function(a,b){var c={},d=l.call(a)=="[object Function]",f;for(f in a)!(d&&f=="prototype")&&!m.call(c,f)&&(c[f]=1)&&m.call(a,f)&&b(f)}:function(a,b){var c=l.call(a)=="[object Function]",d,f;for(d in a)!(c&&d=="prototype")&&m.call(a,d)&&!(f=d==="constructor")&&b(d);(f||m.call(a,d="constructor"))&&b(d)};else{j=["valueOf","toString","toLocaleString", 8 | "propertyIsEnumerable","isPrototypeOf","hasOwnProperty","constructor"];a=function(a,b){var c=l.call(a)=="[object Function]",d;for(d in a)!(c&&d=="prototype")&&m.call(a,d)&&b(d);for(c=j.length;d=j[--c];m.call(a,d)&&b(d));}}a(b,c)};R("json-stringify")||(r={"\\":"\\\\",'"':'\\"',"\u0008":"\\b","\u000c":"\\f","\n":"\\n","\r":"\\r","\t":"\\t"},t=function(b,c){return("000000"+(c||0)).slice(-b)},u=function(b){for(var c='"',a=0,d;d=b.charAt(a);a++)c=c+('\\"\u0008\u000c\n\r\t'.indexOf(d)>-1?r[d]:r[d]=d<" "? 9 | "\\u00"+t(2,d.charCodeAt(0).toString(16)):d);return c+'"'},x=function(b,c,a,d,j,f,o){var g=c[b],h,s,v,w,L,M,N,y,A;if(typeof g=="object"&&g){h=l.call(g);if(h=="[object Date]"&&!m.call(g,"toJSON"))if(g>-1/0&&g<1/0){if(P){v=K(g/864E5);for(h=K(v/365.2425)+1970-1;P(h+1,0)<=v;h++);for(s=K((v-P(h,0))/30.42);P(h,s+1)<=v;s++);v=1+v-P(h,s);w=(g%864E5+864E5)%864E5;L=K(w/36E5)%24;M=K(w/6E4)%60;N=K(w/1E3)%60;w=w%1E3}else{h=g.getUTCFullYear();s=g.getUTCMonth();v=g.getUTCDate();L=g.getUTCHours();M=g.getUTCMinutes(); 10 | N=g.getUTCSeconds();w=g.getUTCMilliseconds()}g=(h<=0||h>=1E4?(h<0?"-":"+")+t(6,h<0?-h:h):t(4,h))+"-"+t(2,s+1)+"-"+t(2,v)+"T"+t(2,L)+":"+t(2,M)+":"+t(2,N)+"."+t(3,w)+"Z"}else g=k;else if(typeof g.toJSON=="function"&&(h!="[object Number]"&&h!="[object String]"&&h!="[object Array]"||m.call(g,"toJSON")))g=g.toJSON(b)}a&&(g=a.call(c,b,g));if(g===k)return"null";h=l.call(g);if(h=="[object Boolean]")return""+g;if(h=="[object Number]")return g>-1/0&&g<1/0?""+g:"null";if(h=="[object String]")return u(g);if(typeof g== 11 | "object"){for(b=o.length;b--;)if(o[b]===g)throw TypeError();o.push(g);y=[];c=f;f=f+j;if(h=="[object Array]"){s=0;for(b=g.length;s0){d="";for(a>10&&(a=10);d.length-1)H++;else{if("{}[]:,".indexOf(a)>-1){H++;return a}if(a=='"'){d="@";for(H++;H-1){d=d+B[a];H++}else if(a=="u"){j=++H;for(f=H+4;H="0"&&a<="9"||a>="a"&&a<="f"||a>="A"&&a<="F"||C()}d=d+z("0x"+b.slice(j,H))}else C()}else{if(a=='"')break; 14 | d=d+a;H++}}if(b.charAt(H)=='"'){H++;return d}}else{j=H;if(a=="-"){o=i;a=b.charAt(++H)}if(a>="0"&&a<="9"){for(a=="0"&&(a=b.charAt(H+1),a>="0"&&a<="9")&&C();H="0"&&a<="9");H++);if(b.charAt(H)=="."){for(f=++H;f="0"&&a<="9");f++);f==H&&C();H=f}a=b.charAt(H);if(a=="e"||a=="E"){a=b.charAt(++H);(a=="+"||a=="-")&&H++;for(f=H;f="0"&&a<="9");f++);f==H&&C();H=f}return+b.slice(j,H)}o&&C();if(b.slice(H,H+4)=="true"){H=H+4;return i}if(b.slice(H,H+5)== 15 | "false"){H=H+5;return false}if(b.slice(H,H+4)=="null"){H=H+4;return k}}C()}}return"$"},E=function(b){var c,a;b=="$"&&C();if(typeof b=="string"){if(b.charAt(0)=="@")return b.slice(1);if(b=="["){for(c=[];;a||(a=i)){b=D();if(b=="]")break;if(a)if(b==","){b=D();b=="]"&&C()}else C();b==","&&C();c.push(E(b))}return c}if(b=="{"){for(c={};;a||(a=i)){b=D();if(b=="}")break;if(a)if(b==","){b=D();b=="}"&&C()}else C();(b==","||typeof b!="string"||b.charAt(0)!="@"||D()!=":")&&C();c[b.slice(1)]=E(D())}return c}C()}return b}, 16 | G=function(b,c,a){a=F(b,c,a);a===e?delete b[c]:b[c]=a},F=function(b,c,a){var d=b[c],j;if(typeof d=="object"&&d)if(l.call(d)=="[object Array]")for(j=d.length;j--;)G(d,j,a);else n(d,function(b){G(d,b,a)});return a.call(b,c,d)},q.parse=function(b,c){var a,d;H=0;I=b;a=E(D());D()!="$"&&C();H=I=k;return c&&l.call(c)=="[object Function]"?F((d={},d[""]=a,d),"",c):a})}p&&define(function(){return q}); 17 | }()); -------------------------------------------------------------------------------- /sudoku/solution.py: -------------------------------------------------------------------------------- 1 | assignments = [] 2 | 3 | 4 | def assign_value(values, box, value): 5 | """ 6 | Please use this function to update your values dictionary! 7 | Assigns a value to a given box. If it updates the board record it. 8 | """ 9 | 10 | # Don't waste memory appending actions that don't actually change any values 11 | if values[box] == value: 12 | return values 13 | 14 | values[box] = value 15 | if len(value) == 1: 16 | assignments.append(values.copy()) 17 | return values 18 | 19 | 20 | def naked_twins(values): 21 | """Eliminate values using the naked twins strategy. 22 | Args: 23 | values(dict): a dictionary of the form {'box_name': '123456789', ...} 24 | 25 | Returns: 26 | the values dictionary with the naked twins eliminated from peers. 27 | """ 28 | 29 | # Find all instances of naked twins and group by units 30 | naked_twin = dict((unitlist.index(u), [s for s in u for x in u if s != x and len(values[s]) == 2 and values[s] == values[x]]) for u in unitlist); 31 | # Eliminate the naked twins as possibilities for their peers in the same unit 32 | for i in naked_twin: 33 | boxes = naked_twin.get(i) 34 | if boxes is not None and len(boxes) >=2: 35 | digits = values[boxes[0]] 36 | for box in unitlist[i]: 37 | if values[box] != digits: 38 | for d in list(digits): 39 | values = assign_value(values, box, values[box].replace(d, '')); 40 | return values 41 | 42 | def cross(A, B): 43 | #"Cross product of elements in A and elements in B." 44 | return [s+t for s in A for t in B] 45 | 46 | rows = 'ABCDEFGHI' 47 | cols = '123456789' 48 | # Start from row I 49 | rev_rows = rows[::-1] 50 | 51 | boxes = cross(rows, cols) 52 | 53 | row_units = [cross(r, cols) for r in rows] 54 | column_units = [cross(rows, c) for c in cols] 55 | square_units = [cross(rs, cs) for rs in ('ABC','DEF','GHI') for cs in ('123','456','789')] 56 | diag_units = [[rows[i]+cols[i] for i in range(len(rows))]] 57 | diag_units_rev = [[rev_rows[i]+cols[i] for i in range(len(rows))]] 58 | unitlist = row_units + column_units + square_units + diag_units + diag_units_rev 59 | units = dict((s, [u for u in unitlist if s in u]) for s in boxes) 60 | peers = dict((s, set(sum(units[s],[]))-set([s])) for s in boxes) 61 | 62 | 63 | def grid_values(grid): 64 | """ 65 | Convert grid into a dict of {square: char} with '123456789' for empties. 66 | Args: 67 | grid(string) - A grid in string form. 68 | Returns: 69 | A grid in dictionary form 70 | Keys: The boxes, e.g., 'A1' 71 | Values: The value in each box, e.g., '8'. If the box has no value, then the value will be '123456789'. 72 | """ 73 | values = [] 74 | all_digits = '123456789' 75 | for c in grid: 76 | if c == '.': 77 | values.append(all_digits) 78 | elif c in all_digits: 79 | values.append(c) 80 | assert len(values) == 81 81 | return dict(zip(boxes, values)) 82 | 83 | 84 | def display(values): 85 | """ 86 | Display the values as a 2-D grid. 87 | Args: 88 | values(dict): The sudoku in dictionary form 89 | """ 90 | width = 1+max(len(values[s]) for s in boxes) 91 | line = '+'.join(['-'*(width*3)]*3) 92 | for r in rows: 93 | print(''.join(values[r+c].center(width)+('|' if c in '36' else '') 94 | for c in cols)) 95 | if r in 'CF': print(line) 96 | return 97 | 98 | 99 | def eliminate(values): 100 | """Eliminate values from peers of each box with a single value. 101 | 102 | Go through all the boxes, and whenever there is a box with a single value, 103 | eliminate this value from the set of values of all its peers. 104 | 105 | Args: 106 | values: Sudoku in dictionary form. 107 | Returns: 108 | Resulting Sudoku in dictionary form after eliminating values. 109 | """ 110 | solved_values = [box for box in values.keys() if len(values[box]) == 1] 111 | for box in solved_values: 112 | digit = values[box] 113 | for peer in peers[box]: 114 | #values[peer] = values[peer].replace(digit,'') 115 | values = assign_value(values, peer, values[peer].replace(digit, '')) 116 | return values 117 | 118 | 119 | def only_choice(values): 120 | """Finalize all values that are the only choice for a unit. 121 | 122 | Go through all the units, and whenever there is a unit with a value 123 | that only fits in one box, assign the value to this box. 124 | 125 | Input: Sudoku in dictionary form. 126 | Output: Resulting Sudoku in dictionary form after filling in only choices. 127 | """ 128 | # TODO: Implement only choice strategy here 129 | for unit in unitlist: 130 | for digit in '123456789': 131 | dplaces = [box for box in unit if digit in values[box]] 132 | if len(dplaces) == 1: 133 | #values[dplaces[0]] = digit 134 | values = assign_value(values, dplaces[0], digit) 135 | return values 136 | 137 | 138 | def reduce_puzzle(values): 139 | """ 140 | Iterate eliminate(), only_choice() and naked_twins(). If at some point, there is a box with no available values, return False. 141 | If the sudoku is solved, return the sudoku. 142 | If after an iteration of both functions, the sudoku remains the same, return the sudoku. 143 | Input: A sudoku in dictionary form. 144 | Output: The resulting sudoku in dictionary form. 145 | """ 146 | stalled = False 147 | while not stalled: 148 | # Check how many boxes have a determined value 149 | solved_values_before = len([box for box in values.keys() if len(values[box]) == 1]) 150 | # Use the Eliminate Strategy 151 | values = eliminate(values) 152 | # Use the Only Choice Strategy 153 | values = only_choice(values) 154 | # Use Naked Twins Technique 155 | values = naked_twins(values) 156 | # Check how many boxes have a determined value, to compare 157 | solved_values_after = len([box for box in values.keys() if len(values[box]) == 1]) 158 | # If no new values were added, stop the loop. 159 | stalled = solved_values_before == solved_values_after 160 | # Sanity check, return False if there is a box with zero available values: 161 | if len([box for box in values.keys() if len(values[box]) == 0]): 162 | return False 163 | return values 164 | 165 | 166 | def search(values): 167 | #"Using depth-first search and propagation, try all possible values." 168 | # First, reduce the puzzle using the previous function 169 | values = reduce_puzzle(values) 170 | if values is False: 171 | return False ## Failed earlier 172 | if all(len(values[s]) == 1 for s in boxes): 173 | return values ## Solved! 174 | # Choose one of the unfilled squares with the fewest possibilities 175 | n,s = min((len(values[s]), s) for s in boxes if len(values[s]) > 1) 176 | # Now use recurrence to solve each one of the resulting sudokus, and 177 | for value in values[s]: 178 | new_sudoku = values.copy() 179 | new_sudoku[s] = value 180 | attempt = search(new_sudoku) 181 | if attempt: 182 | return attempt 183 | 184 | 185 | def solve(grid): 186 | """ 187 | Find the solution to a Sudoku grid. 188 | Args: 189 | grid(string): a string representing a sudoku grid. 190 | Example: '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3' 191 | Returns: 192 | The dictionary representation of the final sudoku grid. False if no solution exists. 193 | """ 194 | values = grid_values(grid) 195 | return search(values) 196 | 197 | 198 | if __name__ == '__main__': 199 | diag_sudoku_grid = '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3' 200 | print (' ------ Unresolved Sudoku ------ ') 201 | display(dict(zip(boxes, diag_sudoku_grid))) 202 | print (' ------ Resolved Sudoku ------ ') 203 | display(solve(diag_sudoku_grid)) 204 | 205 | try: 206 | from visualize import visualize_assignments 207 | visualize_assignments(assignments) 208 | 209 | except SystemExit: 210 | pass 211 | except: 212 | print('We could not visualize your board due to a pygame issue. Not a problem! It is not a requirement.') 213 | -------------------------------------------------------------------------------- /sudoku/solution_test.py: -------------------------------------------------------------------------------- 1 | import solution 2 | import unittest 3 | 4 | 5 | class TestNakedTwins(unittest.TestCase): 6 | before_naked_twins_1 = {'I6': '4', 'H9': '3', 'I2': '6', 'E8': '1', 'H3': '5', 'H7': '8', 'I7': '1', 'I4': '8', 7 | 'H5': '6', 'F9': '7', 'G7': '6', 'G6': '3', 'G5': '2', 'E1': '8', 'G3': '1', 'G2': '8', 8 | 'G1': '7', 'I1': '23', 'C8': '5', 'I3': '23', 'E5': '347', 'I5': '5', 'C9': '1', 'G9': '5', 9 | 'G8': '4', 'A1': '1', 'A3': '4', 'A2': '237', 'A5': '9', 'A4': '2357', 'A7': '27', 10 | 'A6': '257', 'C3': '8', 'C2': '237', 'C1': '23', 'E6': '579', 'C7': '9', 'C6': '6', 11 | 'C5': '37', 'C4': '4', 'I9': '9', 'D8': '8', 'I8': '7', 'E4': '6', 'D9': '6', 'H8': '2', 12 | 'F6': '125', 'A9': '8', 'G4': '9', 'A8': '6', 'E7': '345', 'E3': '379', 'F1': '6', 13 | 'F2': '4', 'F3': '23', 'F4': '1235', 'F5': '8', 'E2': '37', 'F7': '35', 'F8': '9', 14 | 'D2': '1', 'H1': '4', 'H6': '17', 'H2': '9', 'H4': '17', 'D3': '2379', 'B4': '27', 15 | 'B5': '1', 'B6': '8', 'B7': '27', 'E9': '2', 'B1': '9', 'B2': '5', 'B3': '6', 'D6': '279', 16 | 'D7': '34', 'D4': '237', 'D5': '347', 'B8': '3', 'B9': '4', 'D1': '5'} 17 | possible_solutions_1 = [ 18 | {'G7': '6', 'G6': '3', 'G5': '2', 'G4': '9', 'G3': '1', 'G2': '8', 'G1': '7', 'G9': '5', 'G8': '4', 'C9': '1', 19 | 'C8': '5', 'C3': '8', 'C2': '237', 'C1': '23', 'C7': '9', 'C6': '6', 'C5': '37', 'A4': '2357', 'A9': '8', 20 | 'A8': '6', 'F1': '6', 'F2': '4', 'F3': '23', 'F4': '1235', 'F5': '8', 'F6': '125', 'F7': '35', 'F8': '9', 21 | 'F9': '7', 'B4': '27', 'B5': '1', 'B6': '8', 'B7': '27', 'E9': '2', 'B1': '9', 'B2': '5', 'B3': '6', 'C4': '4', 22 | 'B8': '3', 'B9': '4', 'I9': '9', 'I8': '7', 'I1': '23', 'I3': '23', 'I2': '6', 'I5': '5', 'I4': '8', 'I7': '1', 23 | 'I6': '4', 'A1': '1', 'A3': '4', 'A2': '237', 'A5': '9', 'E8': '1', 'A7': '27', 'A6': '257', 'E5': '347', 24 | 'E4': '6', 'E7': '345', 'E6': '579', 'E1': '8', 'E3': '79', 'E2': '37', 'H8': '2', 'H9': '3', 'H2': '9', 25 | 'H3': '5', 'H1': '4', 'H6': '17', 'H7': '8', 'H4': '17', 'H5': '6', 'D8': '8', 'D9': '6', 'D6': '279', 26 | 'D7': '34', 'D4': '237', 'D5': '347', 'D2': '1', 'D3': '79', 'D1': '5'}, 27 | {'I6': '4', 'H9': '3', 'I2': '6', 'E8': '1', 'H3': '5', 'H7': '8', 'I7': '1', 'I4': '8', 'H5': '6', 'F9': '7', 28 | 'G7': '6', 'G6': '3', 'G5': '2', 'E1': '8', 'G3': '1', 'G2': '8', 'G1': '7', 'I1': '23', 'C8': '5', 'I3': '23', 29 | 'E5': '347', 'I5': '5', 'C9': '1', 'G9': '5', 'G8': '4', 'A1': '1', 'A3': '4', 'A2': '237', 'A5': '9', 30 | 'A4': '2357', 'A7': '27', 'A6': '257', 'C3': '8', 'C2': '237', 'C1': '23', 'E6': '579', 'C7': '9', 'C6': '6', 31 | 'C5': '37', 'C4': '4', 'I9': '9', 'D8': '8', 'I8': '7', 'E4': '6', 'D9': '6', 'H8': '2', 'F6': '125', 32 | 'A9': '8', 'G4': '9', 'A8': '6', 'E7': '345', 'E3': '79', 'F1': '6', 'F2': '4', 'F3': '23', 'F4': '1235', 33 | 'F5': '8', 'E2': '3', 'F7': '35', 'F8': '9', 'D2': '1', 'H1': '4', 'H6': '17', 'H2': '9', 'H4': '17', 34 | 'D3': '79', 'B4': '27', 'B5': '1', 'B6': '8', 'B7': '27', 'E9': '2', 'B1': '9', 'B2': '5', 'B3': '6', 35 | 'D6': '279', 'D7': '34', 'D4': '237', 'D5': '347', 'B8': '3', 'B9': '4', 'D1': '5'} 36 | ] 37 | 38 | before_naked_twins_2 = {'A1': '23', 'A2': '4', 'A3': '7', 'A4': '6', 'A5': '8', 'A6': '5', 'A7': '23', 'A8': '9', 39 | 'A9': '1', 'B1': '6', 'B2': '9', 'B3': '8', 'B4': '4', 'B5': '37', 'B6': '1', 'B7': '237', 40 | 'B8': '5', 'B9': '237', 'C1': '23', 'C2': '5', 'C3': '1', 'C4': '23', 'C5': '379', 41 | 'C6': '2379', 'C7': '8', 'C8': '6', 'C9': '4', 'D1': '8', 'D2': '17', 'D3': '9', 42 | 'D4': '1235', 'D5': '6', 'D6': '237', 'D7': '4', 'D8': '27', 'D9': '2357', 'E1': '5', 43 | 'E2': '6', 'E3': '2', 'E4': '8', 'E5': '347', 'E6': '347', 'E7': '37', 'E8': '1', 'E9': '9', 44 | 'F1': '4', 'F2': '17', 'F3': '3', 'F4': '125', 'F5': '579', 'F6': '279', 'F7': '6', 45 | 'F8': '8', 'F9': '257', 'G1': '1', 'G2': '8', 'G3': '6', 'G4': '35', 'G5': '345', 46 | 'G6': '34', 'G7': '9', 'G8': '27', 'G9': '27', 'H1': '7', 'H2': '2', 'H3': '4', 'H4': '9', 47 | 'H5': '1', 'H6': '8', 'H7': '5', 'H8': '3', 'H9': '6', 'I1': '9', 'I2': '3', 'I3': '5', 48 | 'I4': '7', 'I5': '2', 'I6': '6', 'I7': '1', 'I8': '4', 'I9': '8'} 49 | possible_solutions_2 = [ 50 | {'A1': '23', 'A2': '4', 'A3': '7', 'A4': '6', 'A5': '8', 'A6': '5', 'A7': '23', 'A8': '9', 'A9': '1', 'B1': '6', 51 | 'B2': '9', 'B3': '8', 'B4': '4', 'B5': '37', 'B6': '1', 'B7': '237', 'B8': '5', 'B9': '237', 'C1': '23', 52 | 'C2': '5', 'C3': '1', 'C4': '23', 'C5': '79', 'C6': '79', 'C7': '8', 'C8': '6', 'C9': '4', 'D1': '8', 53 | 'D2': '17', 'D3': '9', 'D4': '1235', 'D5': '6', 'D6': '237', 'D7': '4', 'D8': '27', 'D9': '2357', 'E1': '5', 54 | 'E2': '6', 'E3': '2', 'E4': '8', 'E5': '347', 'E6': '347', 'E7': '37', 'E8': '1', 'E9': '9', 'F1': '4', 55 | 'F2': '17', 'F3': '3', 'F4': '125', 'F5': '579', 'F6': '279', 'F7': '6', 'F8': '8', 'F9': '257', 'G1': '1', 56 | 'G2': '8', 'G3': '6', 'G4': '35', 'G5': '345', 'G6': '34', 'G7': '9', 'G8': '27', 'G9': '27', 'H1': '7', 57 | 'H2': '2', 'H3': '4', 'H4': '9', 'H5': '1', 'H6': '8', 'H7': '5', 'H8': '3', 'H9': '6', 'I1': '9', 'I2': '3', 58 | 'I3': '5', 'I4': '7', 'I5': '2', 'I6': '6', 'I7': '1', 'I8': '4', 'I9': '8'}, 59 | {'A1': '23', 'A2': '4', 'A3': '7', 'A4': '6', 'A5': '8', 'A6': '5', 'A7': '23', 'A8': '9', 'A9': '1', 'B1': '6', 60 | 'B2': '9', 'B3': '8', 'B4': '4', 'B5': '3', 'B6': '1', 'B7': '237', 'B8': '5', 'B9': '237', 'C1': '23', 61 | 'C2': '5', 'C3': '1', 'C4': '23', 'C5': '79', 'C6': '79', 'C7': '8', 'C8': '6', 'C9': '4', 'D1': '8', 62 | 'D2': '17', 'D3': '9', 'D4': '1235', 'D5': '6', 'D6': '237', 'D7': '4', 'D8': '27', 'D9': '2357', 'E1': '5', 63 | 'E2': '6', 'E3': '2', 'E4': '8', 'E5': '347', 'E6': '347', 'E7': '37', 'E8': '1', 'E9': '9', 'F1': '4', 64 | 'F2': '17', 'F3': '3', 'F4': '125', 'F5': '579', 'F6': '279', 'F7': '6', 'F8': '8', 'F9': '257', 'G1': '1', 65 | 'G2': '8', 'G3': '6', 'G4': '35', 'G5': '345', 'G6': '34', 'G7': '9', 'G8': '27', 'G9': '27', 'H1': '7', 66 | 'H2': '2', 'H3': '4', 'H4': '9', 'H5': '1', 'H6': '8', 'H7': '5', 'H8': '3', 'H9': '6', 'I1': '9', 'I2': '3', 67 | 'I3': '5', 'I4': '7', 'I5': '2', 'I6': '6', 'I7': '1', 'I8': '4', 'I9': '8'} 68 | ] 69 | 70 | def test_naked_twins(self): 71 | self.assertTrue(solution.naked_twins(self.before_naked_twins_1) in self.possible_solutions_1, 72 | "Your naked_twins function produced an unexpected board.") 73 | 74 | def test_naked_twins2(self): 75 | self.assertTrue(solution.naked_twins(self.before_naked_twins_2) in self.possible_solutions_2, 76 | "Your naked_twins function produced an unexpected board.") 77 | 78 | 79 | 80 | class TestDiagonalSudoku(unittest.TestCase): 81 | diagonal_grid = '2.............62....1....7...6..8...3...9...7...6..4...4....8....52.............3' 82 | solved_diag_sudoku = {'G7': '8', 'G6': '9', 'G5': '7', 'G4': '3', 'G3': '2', 'G2': '4', 'G1': '6', 'G9': '5', 83 | 'G8': '1', 'C9': '6', 'C8': '7', 'C3': '1', 'C2': '9', 'C1': '4', 'C7': '5', 'C6': '3', 84 | 'C5': '2', 'C4': '8', 'E5': '9', 'E4': '1', 'F1': '1', 'F2': '2', 'F3': '9', 'F4': '6', 85 | 'F5': '5', 'F6': '7', 'F7': '4', 'F8': '3', 'F9': '8', 'B4': '7', 'B5': '1', 'B6': '6', 86 | 'B7': '2', 'B1': '8', 'B2': '5', 'B3': '3', 'B8': '4', 'B9': '9', 'I9': '3', 'I8': '2', 87 | 'I1': '7', 'I3': '8', 'I2': '1', 'I5': '6', 'I4': '5', 'I7': '9', 'I6': '4', 'A1': '2', 88 | 'A3': '7', 'A2': '6', 'E9': '7', 'A4': '9', 'A7': '3', 'A6': '5', 'A9': '1', 'A8': '8', 89 | 'E7': '6', 'E6': '2', 'E1': '3', 'E3': '4', 'E2': '8', 'E8': '5', 'A5': '4', 'H8': '6', 90 | 'H9': '4', 'H2': '3', 'H3': '5', 'H1': '9', 'H6': '1', 'H7': '7', 'H4': '2', 'H5': '8', 91 | 'D8': '9', 'D9': '2', 'D6': '8', 'D7': '1', 'D4': '4', 'D5': '3', 'D2': '7', 'D3': '6', 92 | 'D1': '5'} 93 | 94 | def test_solve(self): 95 | self.assertEqual(solution.solve(self.diagonal_grid), self.solved_diag_sudoku) 96 | 97 | if __name__ == '__main__': 98 | unittest.main() 99 | -------------------------------------------------------------------------------- /isolation/sample_players.py: -------------------------------------------------------------------------------- 1 | """This file contains a collection of player classes for comparison with your 2 | own agent and example heuristic functions. 3 | 4 | ************************************************************************ 5 | *********** YOU DO NOT NEED TO MODIFY ANYTHING IN THIS FILE ********** 6 | ************************************************************************ 7 | """ 8 | 9 | from random import randint 10 | 11 | 12 | def null_score(game, player): 13 | """This heuristic presumes no knowledge for non-terminal states, and 14 | returns the same uninformative value for all other states. 15 | 16 | Parameters 17 | ---------- 18 | game : `isolation.Board` 19 | An instance of `isolation.Board` encoding the current state of the 20 | game (e.g., player locations and blocked cells). 21 | 22 | player : hashable 23 | One of the objects registered by the game object as a valid player. 24 | (i.e., `player` should be either game.__player_1__ or 25 | game.__player_2__). 26 | 27 | Returns 28 | ---------- 29 | float 30 | The heuristic value of the current game state. 31 | """ 32 | 33 | if game.is_loser(player): 34 | return float("-inf") 35 | 36 | if game.is_winner(player): 37 | return float("inf") 38 | 39 | return 0. 40 | 41 | 42 | def open_move_score(game, player): 43 | """The basic evaluation function described in lecture that outputs a score 44 | equal to the number of moves open for your computer player on the board. 45 | 46 | Parameters 47 | ---------- 48 | game : `isolation.Board` 49 | An instance of `isolation.Board` encoding the current state of the 50 | game (e.g., player locations and blocked cells). 51 | 52 | player : hashable 53 | One of the objects registered by the game object as a valid player. 54 | (i.e., `player` should be either game.__player_1__ or 55 | game.__player_2__). 56 | 57 | Returns 58 | ---------- 59 | float 60 | The heuristic value of the current game state 61 | """ 62 | if game.is_loser(player): 63 | return float("-inf") 64 | 65 | if game.is_winner(player): 66 | return float("inf") 67 | 68 | return float(len(game.get_legal_moves(player))) 69 | 70 | 71 | def improved_score(game, player): 72 | """The "Improved" evaluation function discussed in lecture that outputs a 73 | score equal to the difference in the number of moves available to the 74 | two players. 75 | 76 | Parameters 77 | ---------- 78 | game : `isolation.Board` 79 | An instance of `isolation.Board` encoding the current state of the 80 | game (e.g., player locations and blocked cells). 81 | 82 | player : hashable 83 | One of the objects registered by the game object as a valid player. 84 | (i.e., `player` should be either game.__player_1__ or 85 | game.__player_2__). 86 | 87 | Returns 88 | ---------- 89 | float 90 | The heuristic value of the current game state 91 | """ 92 | if game.is_loser(player): 93 | return float("-inf") 94 | 95 | if game.is_winner(player): 96 | return float("inf") 97 | 98 | own_moves = len(game.get_legal_moves(player)) 99 | opp_moves = len(game.get_legal_moves(game.get_opponent(player))) 100 | return float(own_moves - opp_moves) 101 | 102 | 103 | def center_score(game, player): 104 | """Outputs a score equal to square of the distance from the center of the 105 | board to the position of the player. 106 | 107 | This heuristic is only used by the autograder for testing. 108 | 109 | Parameters 110 | ---------- 111 | game : `isolation.Board` 112 | An instance of `isolation.Board` encoding the current state of the 113 | game (e.g., player locations and blocked cells). 114 | 115 | player : hashable 116 | One of the objects registered by the game object as a valid player. 117 | (i.e., `player` should be either game.__player_1__ or 118 | game.__player_2__). 119 | 120 | Returns 121 | ---------- 122 | float 123 | The heuristic value of the current game state 124 | """ 125 | if game.is_loser(player): 126 | return float("-inf") 127 | 128 | if game.is_winner(player): 129 | return float("inf") 130 | 131 | w, h = game.width / 2., game.height / 2. 132 | y, x = game.get_player_location(player) 133 | return float((h - y)**2 + (w - x)**2) 134 | 135 | 136 | class RandomPlayer(): 137 | """Player that chooses a move randomly.""" 138 | 139 | def get_move(self, game, time_left): 140 | """Randomly select a move from the available legal moves. 141 | 142 | Parameters 143 | ---------- 144 | game : `isolation.Board` 145 | An instance of `isolation.Board` encoding the current state of the 146 | game (e.g., player locations and blocked cells). 147 | 148 | time_left : callable 149 | A function that returns the number of milliseconds left in the 150 | current turn. Returning with any less than 0 ms remaining forfeits 151 | the game. 152 | 153 | Returns 154 | ---------- 155 | (int, int) 156 | A randomly selected legal move; may return (-1, -1) if there are 157 | no available legal moves. 158 | """ 159 | legal_moves = game.get_legal_moves() 160 | if not legal_moves: 161 | return (-1, -1) 162 | return legal_moves[randint(0, len(legal_moves) - 1)] 163 | 164 | 165 | class GreedyPlayer(): 166 | """Player that chooses next move to maximize heuristic score. This is 167 | equivalent to a minimax search agent with a search depth of one. 168 | """ 169 | 170 | def __init__(self, score_fn=open_move_score): 171 | self.score = score_fn 172 | 173 | def get_move(self, game, time_left): 174 | """Select the move from the available legal moves with the highest 175 | heuristic score. 176 | 177 | Parameters 178 | ---------- 179 | game : `isolation.Board` 180 | An instance of `isolation.Board` encoding the current state of the 181 | game (e.g., player locations and blocked cells). 182 | 183 | time_left : callable 184 | A function that returns the number of milliseconds left in the 185 | current turn. Returning with any less than 0 ms remaining forfeits 186 | the game. 187 | 188 | Returns 189 | ---------- 190 | (int, int) 191 | The move in the legal moves list with the highest heuristic score 192 | for the current game state; may return (-1, -1) if there are no 193 | legal moves. 194 | """ 195 | legal_moves = game.get_legal_moves() 196 | if not legal_moves: 197 | return (-1, -1) 198 | _, move = max([(self.score(game.forecast_move(m), self), m) for m in legal_moves]) 199 | return move 200 | 201 | 202 | class HumanPlayer(): 203 | """Player that chooses a move according to user's input.""" 204 | 205 | def get_move(self, game, time_left): 206 | """ 207 | Select a move from the available legal moves based on user input at the 208 | terminal. 209 | 210 | ********************************************************************** 211 | NOTE: If testing with this player, remember to disable move timeout in 212 | the call to `Board.play()`. 213 | ********************************************************************** 214 | 215 | Parameters 216 | ---------- 217 | game : `isolation.Board` 218 | An instance of `isolation.Board` encoding the current state of the 219 | game (e.g., player locations and blocked cells). 220 | 221 | time_left : callable 222 | A function that returns the number of milliseconds left in the 223 | current turn. Returning with any less than 0 ms remaining forfeits 224 | the game. 225 | 226 | Returns 227 | ---------- 228 | (int, int) 229 | The move in the legal moves list selected by the user through the 230 | terminal prompt; automatically return (-1, -1) if there are no 231 | legal moves 232 | """ 233 | legal_moves = game.get_legal_moves() 234 | if not legal_moves: 235 | return (-1, -1) 236 | 237 | print(game.to_string()) #display the board for the human player 238 | print(('\t'.join(['[%d] %s' % (i, str(move)) for i, move in enumerate(legal_moves)]))) 239 | 240 | valid_choice = False 241 | while not valid_choice: 242 | try: 243 | index = int(input('Select move index:')) 244 | valid_choice = 0 <= index < len(legal_moves) 245 | 246 | if not valid_choice: 247 | print('Illegal move! Try again.') 248 | 249 | except ValueError: 250 | print('Invalid index! Try again.') 251 | 252 | return legal_moves[index] 253 | 254 | 255 | if __name__ == "__main__": 256 | from isolation import Board 257 | 258 | # create an isolation board (by default 7x7) 259 | player1 = RandomPlayer() 260 | player2 = GreedyPlayer() 261 | game = Board(player1, player2) 262 | 263 | # place player 1 on the board at row 2, column 3, then place player 2 on 264 | # the board at row 0, column 5; display the resulting board state. Note 265 | # that the .apply_move() method changes the calling object in-place. 266 | game.apply_move((2, 3)) 267 | game.apply_move((0, 5)) 268 | print(game.to_string()) 269 | 270 | # players take turns moving on the board, so player1 should be next to move 271 | assert(player1 == game.active_player) 272 | 273 | # get a list of the legal moves available to the active player 274 | print(game.get_legal_moves()) 275 | 276 | # get a successor of the current state by making a copy of the board and 277 | # applying a move. Notice that this does NOT change the calling object 278 | # (unlike .apply_move()). 279 | new_game = game.forecast_move((1, 1)) 280 | player2 = new_game.inactive_player 281 | assert(player2 == player1) 282 | assert(new_game.to_string() != game.to_string()) 283 | print("\nOld state:\n{}".format(game.to_string())) 284 | print("\nNew state:\n{}".format(new_game.to_string())) 285 | 286 | # play the remainder of the game automatically -- outcome can be "illegal 287 | # move", "timeout", or "forfeit" 288 | winner, history, outcome = game.play() 289 | print("\nWinner: {}\nOutcome: {}".format(winner, outcome)) 290 | print(game.to_string()) 291 | print("Move history:\n{!s}".format(history)) 292 | -------------------------------------------------------------------------------- /planning/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Implement a Planning Search 3 | 4 | ## Synopsis 5 | 6 | This project includes skeletons for the classes and functions needed to solve deterministic logistics planning problems for an Air Cargo transport system using a planning search agent. 7 | With progression search algorithms like those in the navigation problem from lecture, optimal plans for each 8 | problem will be computed. Unlike the navigation problem, there is no simple distance heuristic to aid the agent. 9 | Instead, you will implement domain-independent heuristics. 10 | ![Progression air cargo search](images/Progression.PNG) 11 | 12 | - Part 1 - Planning problems: 13 | - READ: applicable portions of the Russel/Norvig AIMA text 14 | - GIVEN: problems defined in classical PDDL (Planning Domain Definition Language) 15 | - TODO: Implement the Python methods and functions as marked in `my_air_cargo_problems.py` 16 | - TODO: Experiment and document metrics 17 | - Part 2 - Domain-independent heuristics: 18 | - READ: applicable portions of the Russel/Norvig AIMA text 19 | - TODO: Implement relaxed problem heuristic in `my_air_cargo_problems.py` 20 | - TODO: Implement Planning Graph and automatic heuristic in `my_planning_graph.py` 21 | - TODO: Experiment and document metrics 22 | - Part 3 - Written Analysis 23 | 24 | ## Environment requirements 25 | - Python 3.4 or higher 26 | - Starter code includes a copy of [companion code](https://github.com/aimacode) from the Stuart Russel/Norvig AIMA text. 27 | 28 | 29 | ## Project Details 30 | ### Part 1 - Planning problems 31 | #### READ: Stuart Russel and Peter Norvig text: 32 | 33 | "Artificial Intelligence: A Modern Approach" 3rd edition chapter 10 *or* 2nd edition Chapter 11 on Planning, available [on the AIMA book site](http://aima.cs.berkeley.edu/2nd-ed/newchap11.pdf) sections: 34 | 35 | - *The Planning Problem* 36 | - *Planning with State-space Search* 37 | 38 | #### GIVEN: classical PDDL problems 39 | 40 | All problems are in the Air Cargo domain. They have the same action schema defined, but different initial states and goals. 41 | 42 | - Air Cargo Action Schema: 43 | ``` 44 | Action(Load(c, p, a), 45 | PRECOND: At(c, a) ∧ At(p, a) ∧ Cargo(c) ∧ Plane(p) ∧ Airport(a) 46 | EFFECT: ¬ At(c, a) ∧ In(c, p)) 47 | Action(Unload(c, p, a), 48 | PRECOND: In(c, p) ∧ At(p, a) ∧ Cargo(c) ∧ Plane(p) ∧ Airport(a) 49 | EFFECT: At(c, a) ∧ ¬ In(c, p)) 50 | Action(Fly(p, from, to), 51 | PRECOND: At(p, from) ∧ Plane(p) ∧ Airport(from) ∧ Airport(to) 52 | EFFECT: ¬ At(p, from) ∧ At(p, to)) 53 | ``` 54 | 55 | - Problem 1 initial state and goal: 56 | ``` 57 | Init(At(C1, SFO) ∧ At(C2, JFK) 58 | ∧ At(P1, SFO) ∧ At(P2, JFK) 59 | ∧ Cargo(C1) ∧ Cargo(C2) 60 | ∧ Plane(P1) ∧ Plane(P2) 61 | ∧ Airport(JFK) ∧ Airport(SFO)) 62 | Goal(At(C1, JFK) ∧ At(C2, SFO)) 63 | ``` 64 | - Problem 2 initial state and goal: 65 | ``` 66 | Init(At(C1, SFO) ∧ At(C2, JFK) ∧ At(C3, ATL) 67 | ∧ At(P1, SFO) ∧ At(P2, JFK) ∧ At(P3, ATL) 68 | ∧ Cargo(C1) ∧ Cargo(C2) ∧ Cargo(C3) 69 | ∧ Plane(P1) ∧ Plane(P2) ∧ Plane(P3) 70 | ∧ Airport(JFK) ∧ Airport(SFO) ∧ Airport(ATL)) 71 | Goal(At(C1, JFK) ∧ At(C2, SFO) ∧ At(C3, SFO)) 72 | ``` 73 | - Problem 3 initial state and goal: 74 | ``` 75 | Init(At(C1, SFO) ∧ At(C2, JFK) ∧ At(C3, ATL) ∧ At(C4, ORD) 76 | ∧ At(P1, SFO) ∧ At(P2, JFK) 77 | ∧ Cargo(C1) ∧ Cargo(C2) ∧ Cargo(C3) ∧ Cargo(C4) 78 | ∧ Plane(P1) ∧ Plane(P2) 79 | ∧ Airport(JFK) ∧ Airport(SFO) ∧ Airport(ATL) ∧ Airport(ORD)) 80 | Goal(At(C1, JFK) ∧ At(C3, JFK) ∧ At(C2, SFO) ∧ At(C4, SFO)) 81 | ``` 82 | 83 | #### TODO: Implement methods and functions in `my_air_cargo_problems.py` 84 | - `AirCargoProblem.get_actions` method including `load_actions` and `unload_actions` sub-functions 85 | - `AirCargoProblem.actions` method 86 | - `AirCargoProblem.result` method 87 | - `air_cargo_p2` function 88 | - `air_cargo_p3` function 89 | 90 | #### TODO: Experiment and document metrics for non-heuristic planning solution searches 91 | * Run uninformed planning searches for `air_cargo_p1`, `air_cargo_p2`, and `air_cargo_p3`; provide metrics on number of node expansions required, number of goal tests, time elapsed, and optimality of solution for each search algorithm. Include the result of at least three of these searches, including breadth-first and depth-first, in your write-up (`breadth_first_search` and `depth_first_graph_search`). 92 | * If depth-first takes longer than 10 minutes for Problem 3 on your system, stop the search and provide this information in your report. 93 | * Use the `run_search` script for your data collection: from the command line type `python run_search.py -h` to learn more. 94 | 95 | >#### Why are we setting the problems up this way? 96 | >Progression planning problems can be 97 | solved with graph searches such as breadth-first, depth-first, and A*, where the 98 | nodes of the graph are "states" and edges are "actions". A "state" is the logical 99 | conjunction of all boolean ground "fluents", or state variables, that are possible 100 | for the problem using Propositional Logic. For example, we might have a problem to 101 | plan the transport of one cargo, C1, on a 102 | single available plane, P1, from one airport to another, SFO to JFK. 103 | ![state space](images/statespace.png) 104 | In this simple example, there are five fluents, or state variables, which means our state 105 | space could be as large as ![2to5](images/twotofive.png). Note the following: 106 | >- While the initial state defines every fluent explicitly, in this case mapped to **TTFFF**, the goal may 107 | be a set of states. Any state that is `True` for the fluent `At(C1,JFK)` meets the goal. 108 | >- Even though PDDL uses variable to describe actions as "action schema", these problems 109 | are not solved with First Order Logic. They are solved with Propositional logic and must 110 | therefore be defined with concrete (non-variable) actions 111 | and literal (non-variable) fluents in state descriptions. 112 | >- The fluents here are mapped to a simple string representing the boolean value of each fluent 113 | in the system, e.g. **TTFFTT...TTF**. This will be the state representation in 114 | the `AirCargoProblem` class and is compatible with the `Node` and `Problem` 115 | classes, and the search methods in the AIMA library. 116 | 117 | 118 | ### Part 2 - Domain-independent heuristics 119 | #### READ: Stuart Russel and Peter Norvig text 120 | "Artificial Intelligence: A Modern Approach" 3rd edition chapter 10 *or* 2nd edition Chapter 11 on Planning, available [on the AIMA book site](http://aima.cs.berkeley.edu/2nd-ed/newchap11.pdf) section: 121 | 122 | - *Planning Graph* 123 | 124 | #### TODO: Implement heuristic method in `my_air_cargo_problems.py` 125 | - `AirCargoProblem.h_ignore_preconditions` method 126 | 127 | #### TODO: Implement a Planning Graph with automatic heuristics in `my_planning_graph.py` 128 | - `PlanningGraph.add_action_level` method 129 | - `PlanningGraph.add_literal_level` method 130 | - `PlanningGraph.inconsistent_effects_mutex` method 131 | - `PlanningGraph.interference_mutex` method 132 | - `PlanningGraph.competing_needs_mutex` method 133 | - `PlanningGraph.negation_mutex` method 134 | - `PlanningGraph.inconsistent_support_mutex` method 135 | - `PlanningGraph.h_levelsum` method 136 | 137 | 138 | #### TODO: Experiment and document: metrics of A* searches with these heuristics 139 | * Run A* planning searches using the heuristics you have implemented on `air_cargo_p1`, `air_cargo_p2` and `air_cargo_p3`. Provide metrics on number of node expansions required, number of goal tests, time elapsed, and optimality of solution for each search algorithm and include the results in your report. 140 | * Use the `run_search` script for this purpose: from the command line type `python run_search.py -h` to learn more. 141 | 142 | >#### Why a Planning Graph? 143 | >The planning graph is somewhat complex, but is useful in planning because it is a polynomial-size approximation of the exponential tree that represents all possible paths. The planning graph can be used to provide automated admissible heuristics for any domain. It can also be used as the first step in implementing GRAPHPLAN, a direct planning algorithm that you may wish to learn more about on your own (but we will not address it here). 144 | 145 | >*Planning Graph example from the AIMA book* 146 | >![Planning Graph](images/eatcake-graphplan2.png) 147 | 148 | ### Part 3: Written Analysis 149 | #### TODO: Include the following in your written analysis. 150 | - Provide an optimal plan for Problems 1, 2, and 3. 151 | - Compare and contrast non-heuristic search result metrics (optimality, time elapsed, number of node expansions) for Problems 1,2, and 3. Include breadth-first, depth-first, and at least one other uninformed non-heuristic search in your comparison; Your third choice of non-heuristic search may be skipped for Problem 3 if it takes longer than 10 minutes to run, but a note in this case should be included. 152 | - Compare and contrast heuristic search result metrics using A* with the "ignore preconditions" and "level-sum" heuristics for Problems 1, 2, and 3. 153 | - What was the best heuristic used in these problems? Was it better than non-heuristic search planning methods for all problems? Why or why not? 154 | - Provide tables or other visual aids as needed for clarity in your discussion. 155 | 156 | ## Examples and Testing: 157 | - The planning problem for the "Have Cake and Eat it Too" problem in the book has been 158 | implemented in the `example_have_cake` module as an example. 159 | - The `tests` directory includes `unittest` test cases to evaluate your implementations. All tests should pass before you submit your project for review. From the AIND-Planning directory command line: 160 | - `python -m unittest tests.test_my_air_cargo_problems` 161 | - `python -m unittest tests.test_my_planning_graph` 162 | - The `run_search` script is provided for gathering metrics for various search methods on any or all of the problems and should be used for this purpose. 163 | 164 | ## Submission 165 | Before submitting your solution to a reviewer, you are required to submit your project to Udacity's Project Assistant, which will provide some initial feedback. 166 | 167 | The setup is simple. If you have not installed the client tool already, then you may do so with the command `pip install udacity-pa`. 168 | 169 | To submit your code to the project assistant, run `udacity submit` from within the top-level directory of this project. You will be prompted for a username and password. If you login using google or facebook, visit [this link](https://project-assistant.udacity.com/auth_tokens/jwt_login) for alternate login instructions. 170 | 171 | This process will create a zipfile in your top-level directory named cargo_planning-.zip. This is the file that you should submit to the Udacity reviews system. 172 | 173 | ## Improving Execution Time 174 | 175 | The exercises in this project can take a *long* time to run (from several seconds to a several hours) depending on the heuristics and search algorithms you choose, as well as the efficiency of your own code. (You may want to stop and profile your code if runtimes stretch past a few minutes.) One option to improve execution time is to try installing and using [pypy3](http://pypy.org/download.html) -- a python JIT, which can accelerate execution time substantially. Using pypy is *not* required (and thus not officially supported) -- an efficient solution to this project runs in very reasonable time on modest hardware -- but working with pypy may allow students to explore more sophisticated problems than the examples included in the project. 176 | 177 | ## Code Review 178 | You can find my project feedback from one of the Udacity reviewers in [here](https://review.udacity.com/#!/reviews/545506/shared) -------------------------------------------------------------------------------- /isolation/README.md: -------------------------------------------------------------------------------- 1 | 2 | # Build a Game-playing Agent 3 | 4 | ![Example game of isolation](viz.gif) 5 | 6 | ## Synopsis 7 | 8 | In this project, students will develop an adversarial search agent to play the game "Isolation". Isolation is a deterministic, two-player game of perfect information in which the players alternate turns moving a single piece from one cell to another on a board. Whenever either player occupies a cell, that cell becomes blocked for the remainder of the game. The first player with no remaining legal moves loses, and the opponent is declared the winner. These rules are implemented in the `isolation.Board` class provided in the repository. 9 | 10 | This project uses a version of Isolation where each agent is restricted to L-shaped movements (like a knight in chess) on a rectangular grid (like a chess or checkerboard). The agents can move to any open cell on the board that is 2-rows and 1-column or 2-columns and 1-row away from their current position on the board. Movements are blocked at the edges of the board (the board does not wrap around), however, the player can "jump" blocked or occupied spaces (just like a knight in chess). 11 | 12 | Additionally, agents will have a fixed time limit each turn to search for the best move and respond. If the time limit expires during a player's turn, that player forfeits the match, and the opponent wins. 13 | 14 | Students only need to modify code in the `game_agent.py` file to complete the project. Additional files include example Player and evaluation functions, the game board class, and a template to develop local unit tests. 15 | 16 | 17 | ## Instructions 18 | 19 | In order to complete the Isolation project, students must submit code that passes all test cases for the required functions in `game_agent.py` and complete a report as specified in the rubric. Students can submit using the [Udacity Project Assistant]() command line utility. Students will receive feedback on test case success/failure after each submission. 20 | 21 | Students must implement the following functions: 22 | 23 | - `MinimaxPlayer.minimax()`: implement minimax search 24 | - `AlphaBetaPlayer.alphabeta()`: implement minimax search with alpha-beta pruning 25 | - `AlphaBetaPlayer.get_move()`: implement iterative deepening search 26 | - `custom_score()`: implement your own best position evaluation heuristic 27 | - `custom_score_2()`: implement your own alternate position evaluation heuristic 28 | - `custom_score_3()`: implement your own alternate position evaluation heuristic 29 | 30 | You may write or modify code within each file (but you must maintain compatibility with the function signatures provided). You may add other classes, functions, etc., as needed, but it is not required. 31 | 32 | The Project Assistant sandbox for this project places some restrictions on the modules available and blocks calls to some of the standard library functions. In general, standard library functions that introspect code running in the sandbox are blocked, and the PA only allows the following modules `random`, `numpy`, `scipy`, `sklearn`, `itertools`, `math`, `heapq`, `collections`, `array`, `copy`, and `operator`. (Modules within these packages are also allowed, e.g., `numpy.random`.) 33 | 34 | 35 | ### Quickstart Guide 36 | 37 | The following example creates a game and illustrates the basic API. You can run this example by activating your aind anaconda environment and executing the command `python sample_players.py` 38 | 39 | from isolation import Board 40 | 41 | # create an isolation board (by default 7x7) 42 | player1 = RandomPlayer() 43 | player2 = GreedyPlayer() 44 | game = Board(player1, player2) 45 | 46 | # place player 1 on the board at row 2, column 3, then place player 2 on 47 | # the board at row 0, column 5; display the resulting board state. Note 48 | # that the .apply_move() method changes the calling object in-place. 49 | game.apply_move((2, 3)) 50 | game.apply_move((0, 5)) 51 | print(game.to_string()) 52 | 53 | # players take turns moving on the board, so player1 should be next to move 54 | assert(player1 == game.active_player) 55 | 56 | # get a list of the legal moves available to the active player 57 | print(game.get_legal_moves()) 58 | 59 | # get a successor of the current state by making a copy of the board and 60 | # applying a move. Notice that this does NOT change the calling object 61 | # (unlike .apply_move()). 62 | new_game = game.forecast_move((1, 1)) 63 | assert(new_game.to_string() != game.to_string()) 64 | print("\nOld state:\n{}".format(game.to_string())) 65 | print("\nNew state:\n{}".format(new_game.to_string())) 66 | 67 | # play the remainder of the game automatically -- outcome can be "illegal 68 | # move", "timeout", or "forfeit" 69 | winner, history, outcome = game.play() 70 | print("\nWinner: {}\nOutcome: {}".format(winner, outcome)) 71 | print(game.to_string()) 72 | print("Move history:\n{!s}".format(history)) 73 | 74 | 75 | ### Coding 76 | 77 | The steps below outline a suggested process for completing the project -- however, this is just a suggestion to help you get started. A stub for writing unit tests is provided in the `agent_test.py` file (no local test cases are provided). (See the [unittest](https://docs.python.org/3/library/unittest.html#basic-example) module for information on getting started.) 78 | 79 | The primary mechanism for testing your code will be the Udacity Project Assistant command line utility. You can install the Udacity-PA tool by activating your aind anaconda environment, then running `pip install udacity-pa`. You can submit your code for scoring by running `udacity submit isolation`. The project assistant server has a collection of unit tests that it will execute on your code, and it will provide feedback on any successes or failures. You must pass all test cases in the project assistant before you can complete the project by submitting your report for review. 80 | 81 | 0. Verify that the Udacity-PA tool is installed properly by submitting the project. Run `udacity submit isolation`. (You should see a list of test cases that failed -- that's expected because you haven't implemented any code yet.) 82 | 83 | 0. Modify the `MinimaxPlayer.minimax()` method to return any legal move for the active player. Resubmit your code to the project assistant and the minimax interface test should pass. 84 | 85 | 0. Further modify the `MinimaxPlayer.minimax()` method to implement the full recursive search procedure described in lecture (ref. [AIMA Minimax Decision](https://github.com/aimacode/aima-pseudocode/blob/master/md/Minimax-Decision.md)). Resubmit your code to the project assistant and both the minimax interface and functional test cases will pass. 86 | 87 | 0. Start on the alpha beta test cases. Modify the `AlphaBetaPlayer.alphabeta()` method to return any legal move for the active player. Resubmit your code to the project assistant and the alphabeta interface test should pass. 88 | 89 | 0. Further modify the `AlphaBetaPlayer.alphabeta()` method to implement the full recursive search procedure described in lecture (ref. [AIMA Alpha-Beta Search](https://github.com/aimacode/aima-pseudocode/blob/master/md/Alpha-Beta-Search.md)). Resubmit your code to the project assistant and both the alphabeta interface and functional test cases will pass. 90 | 91 | 0. You can pass the interface test for the `AlphaBetaPlayer.get_move()` function by copying the code from `MinimaxPlayer.get_move()`. Resubmit your code to the project assistant to see that the `get_move()` interface test case passes. 92 | 93 | 0. Pass the test_get_move test by modifying `AlphaBetaPlayer.get_move()` to implement Iterative Deepening. See Also [AIMA Iterative Deepening Search](https://github.com/aimacode/aima-pseudocode/blob/master/md/Iterative-Deepening-Search.md) 94 | 95 | 0. Finally, pass the heuristic tests by implementing any heuristic in `custom_score()`, `custom_score_2()`, and `custom_score_3()`. (These test cases only validate the return value type -- it does not check for "correctness" of your heuristic.) You can see example heuristics in the `sample_players.py` file. 96 | 97 | 98 | ### Tournament 99 | 100 | The `tournament.py` script is used to evaluate the effectiveness of your custom heuristics. The script measures relative performance of your agent (named "Student" in the tournament) in a round-robin tournament against several other pre-defined agents. The Student agent uses time-limited Iterative Deepening along with your custom heuristics. 101 | 102 | The performance of time-limited iterative deepening search is hardware dependent (faster hardware is expected to search deeper than slower hardware in the same amount of time). The script controls for these effects by also measuring the baseline performance of an agent called "ID_Improved" that uses Iterative Deepening and the improved_score heuristic defined in `sample_players.py`. Your goal is to develop a heuristic such that Student outperforms ID_Improved. (NOTE: This can be _very_ challenging!) 103 | 104 | The tournament opponents are listed below. (See also: sample heuristics and players defined in sample_players.py) 105 | 106 | - Random: An agent that randomly chooses a move each turn. 107 | - MM_Open: MinimaxPlayer agent using the open_move_score heuristic with search depth 3 108 | - MM_Center: MinimaxPlayer agent using the center_score heuristic with search depth 3 109 | - MM_Improved: MinimaxPlayer agent using the improved_score heuristic with search depth 3 110 | - AB_Open: AlphaBetaPlayer using iterative deepening alpha-beta search and the open_move_score heuristic 111 | - AB_Center: AlphaBetaPlayer using iterative deepening alpha-beta search and the center_score heuristic 112 | - AB_Improved: AlphaBetaPlayer using iterative deepening alpha-beta search and the improved_score heuristic 113 | 114 | ## Submission 115 | 116 | Before submitting your solution to a reviewer, you are required to submit your project to Udacity's Project Assistant, which will provide some initial feedback. 117 | 118 | Please see the instructions in the [AIND-Sudoku](https://github.com/udacity/AIND-Sudoku#submission) project repository for installation and setup instructions. 119 | 120 | To submit your code to the project assistant, run `udacity submit isolation` from within the top-level directory of this project. You will be prompted for a username and password. If you login using google or facebook, follow the [instructions for using a jwt](https://project-assistant.udacity.com/faq). 121 | 122 | This process will create a zipfile in your top-level directory named `isolation-.zip`. This is the file that you should submit to the Udacity reviews system. 123 | 124 | 125 | ## Game Visualization 126 | 127 | The `isoviz` folder contains a modified version of chessboard.js that can animate games played on a 7x7 board. In order to use the board, you must run a local webserver by running `python -m http.server 8000` from your project directory (you can replace 8000 with another port number if that one is unavailable), then open your browser to `http://localhost:8000` and navigate to the `/isoviz/display.html` page. Enter the move history of an isolation match (i.e., the array returned by the Board.play() method) into the text area and run the match. Refresh the page to run a different game. (Feel free to submit pull requests with improvements to isoviz.) 128 | 129 | 130 | ## PvP Competition 131 | 132 | Once your project has been reviewed and accepted by meeting all requirements of the rubric, you are invited to complete the `competition_agent.py` file using any combination of techniques and improvements from lectures or online, and then submit it to compete in a tournament against other students from your cohort and past cohort champions. Additional details (official rules, submission deadline, etc.) will be provided separately. 133 | 134 | The competition agent can be submitted using the Udacity project assistant: 135 | 136 | udacity submit isolation-pvp 137 | 138 | ## Code Review 139 | You can find my project feedback from one of the Udacity reviewers in [here](https://review.udacity.com/#!/reviews/497889/shared) -------------------------------------------------------------------------------- /recognizer/asl_data.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | import numpy as np 4 | import pandas as pd 5 | 6 | 7 | class AslDb(object): 8 | """ American Sign Language database drawn from the RWTH-BOSTON-104 frame positional data 9 | 10 | This class has been designed to provide a convenient interface for individual word data for students in the Udacity AI Nanodegree Program. 11 | 12 | For example, to instantiate and load train/test files using a feature_method 13 | definition named features, the following snippet may be used: 14 | asl = AslDb() 15 | asl.build_training(tr_file, features) 16 | asl.build_test(tst_file, features) 17 | 18 | Reference for the original ASL data: 19 | http://www-i6.informatik.rwth-aachen.de/~dreuw/database-rwth-boston-104.php 20 | The sentences provided in the data have been segmented into isolated words for this database 21 | """ 22 | 23 | def __init__(self, 24 | hands_fn=os.path.join('data', 'hands_condensed.csv'), 25 | speakers_fn=os.path.join('data', 'speaker.csv'), 26 | ): 27 | """ loads ASL database from csv files with hand position information by frame, and speaker information 28 | 29 | :param hands_fn: str 30 | filename of hand position csv data with expected format: 31 | video,frame,left-x,left-y,right-x,right-y,nose-x,nose-y 32 | :param speakers_fn: 33 | filename of video speaker csv mapping with expected format: 34 | video,speaker 35 | 36 | Instance variables: 37 | df: pandas dataframe 38 | snippit example: 39 | left-x left-y right-x right-y nose-x nose-y speaker 40 | video frame 41 | 98 0 149 181 170 175 161 62 woman-1 42 | 1 149 181 170 175 161 62 woman-1 43 | 2 149 181 170 175 161 62 woman-1 44 | 45 | """ 46 | self.df = pd.read_csv(hands_fn).merge(pd.read_csv(speakers_fn),on='video') 47 | self.df.set_index(['video','frame'], inplace=True) 48 | 49 | def build_training(self, feature_list, csvfilename =os.path.join('data', 'train_words.csv')): 50 | """ wrapper creates sequence data objects for training words suitable for hmmlearn library 51 | 52 | :param feature_list: list of str label names 53 | :param csvfilename: str 54 | :return: WordsData object 55 | dictionary of lists of feature list sequence lists for each word 56 | {'FRANK': [[[87, 225], [87, 225], ...], [[88, 219], [88, 219], ...]]]} 57 | """ 58 | return WordsData(self, csvfilename, feature_list) 59 | 60 | def build_test(self, feature_method, csvfile=os.path.join('data', 'test_words.csv')): 61 | """ wrapper creates sequence data objects for individual test word items suitable for hmmlearn library 62 | 63 | :param feature_method: Feature function 64 | :param csvfile: str 65 | :return: SinglesData object 66 | dictionary of lists of feature list sequence lists for each indexed 67 | {3: [[[87, 225], [87, 225], ...]]]} 68 | """ 69 | return SinglesData(self, csvfile, feature_method) 70 | 71 | 72 | class WordsData(object): 73 | """ class provides loading and getters for ASL data suitable for use with hmmlearn library 74 | 75 | """ 76 | 77 | def __init__(self, asl:AslDb, csvfile:str, feature_list:list): 78 | """ loads training data sequences suitable for use with hmmlearn library based on feature_method chosen 79 | 80 | :param asl: ASLdata object 81 | :param csvfile: str 82 | filename of csv file containing word training start and end frame data with expected format: 83 | video,speaker,word,startframe,endframe 84 | :param feature_list: list of str feature labels 85 | """ 86 | self._data = self._load_data(asl, csvfile, feature_list) 87 | self._hmm_data = create_hmmlearn_data(self._data) 88 | self.num_items = len(self._data) 89 | self.words = list(self._data.keys()) 90 | 91 | def _load_data(self, asl, fn, feature_list): 92 | """ Consolidates sequenced feature data into a dictionary of words 93 | 94 | :param asl: ASLdata object 95 | :param fn: str 96 | filename of csv file containing word training data 97 | :param feature_list: list of str 98 | :return: dict 99 | """ 100 | tr_df = pd.read_csv(fn) 101 | dict = {} 102 | for i in range(len(tr_df)): 103 | word = tr_df.ix[i,'word'] 104 | video = tr_df.ix[i,'video'] 105 | new_sequence = [] # list of sample lists for a sequence 106 | for frame in range(tr_df.ix[i,'startframe'], tr_df.ix[i,'endframe']+1): 107 | vid_frame = video, frame 108 | sample = [asl.df.ix[vid_frame][f] for f in feature_list] 109 | if len(sample) > 0: # dont add if not found 110 | new_sequence.append(sample) 111 | if word in dict: 112 | dict[word].append(new_sequence) # list of sequences 113 | else: 114 | dict[word] = [new_sequence] 115 | return dict 116 | 117 | def get_all_sequences(self): 118 | """ getter for entire db of words as series of sequences of feature lists for each frame 119 | 120 | :return: dict 121 | dictionary of lists of feature list sequence lists for each word 122 | {'FRANK': [[[87, 225], [87, 225], ...], [[88, 219], [88, 219], ...]]], 123 | ...} 124 | """ 125 | return self._data 126 | 127 | def get_all_Xlengths(self): 128 | """ getter for entire db of words as (X, lengths) tuple for use with hmmlearn library 129 | 130 | :return: dict 131 | dictionary of (X, lengths) tuple, where X is a numpy array of feature lists and lengths is 132 | a list of lengths of sequences within X 133 | {'FRANK': (array([[ 87, 225],[ 87, 225], ... [ 87, 225, 62, 127], [ 87, 225, 65, 128]]), [14, 18]), 134 | ...} 135 | """ 136 | return self._hmm_data 137 | 138 | def get_word_sequences(self, word:str): 139 | """ getter for single word series of sequences of feature lists for each frame 140 | 141 | :param word: str 142 | :return: list 143 | lists of feature list sequence lists for given word 144 | [[[87, 225], [87, 225], ...], [[88, 219], [88, 219], ...]]] 145 | """ 146 | return self._data[word] 147 | 148 | def get_word_Xlengths(self, word:str): 149 | """ getter for single word (X, lengths) tuple for use with hmmlearn library 150 | 151 | :param word: 152 | :return: (list, list) 153 | (X, lengths) tuple, where X is a numpy array of feature lists and lengths is 154 | a list of lengths of sequences within X 155 | (array([[ 87, 225],[ 87, 225], ... [ 87, 225, 62, 127], [ 87, 225, 65, 128]]), [14, 18]) 156 | """ 157 | return self._hmm_data[word] 158 | 159 | 160 | class SinglesData(object): 161 | """ class provides loading and getters for ASL data suitable for use with hmmlearn library 162 | 163 | """ 164 | 165 | def __init__(self, asl:AslDb, csvfile:str, feature_list): 166 | """ loads training data sequences suitable for use with hmmlearn library based on feature_method chosen 167 | 168 | :param asl: ASLdata object 169 | :param csvfile: str 170 | filename of csv file containing word training start and end frame data with expected format: 171 | video,speaker,word,startframe,endframe 172 | :param feature_list: list str of feature labels 173 | """ 174 | self.df = pd.read_csv(csvfile) 175 | self.wordlist = list(self.df['word']) 176 | self.sentences_index = self._load_sentence_word_indices() 177 | self._data = self._load_data(asl, feature_list) 178 | self._hmm_data = create_hmmlearn_data(self._data) 179 | self.num_items = len(self._data) 180 | self.num_sentences = len(self.sentences_index) 181 | 182 | # def _load_data(self, asl, fn, feature_method): 183 | def _load_data(self, asl, feature_list): 184 | """ Consolidates sequenced feature data into a dictionary of words and creates answer list of words in order 185 | of index used for dictionary keys 186 | 187 | :param asl: ASLdata object 188 | :param fn: str 189 | filename of csv file containing word training data 190 | :param feature_method: Feature function 191 | :return: dict 192 | """ 193 | dict = {} 194 | # for each word indexed in the DataFrame 195 | for i in range(len(self.df)): 196 | video = self.df.ix[i,'video'] 197 | new_sequence = [] # list of sample dictionaries for a sequence 198 | for frame in range(self.df.ix[i,'startframe'], self.df.ix[i,'endframe']+1): 199 | vid_frame = video, frame 200 | sample = [asl.df.ix[vid_frame][f] for f in feature_list] 201 | if len(sample) > 0: # dont add if not found 202 | new_sequence.append(sample) 203 | if i in dict: 204 | dict[i].append(new_sequence) # list of sequences 205 | else: 206 | dict[i] = [new_sequence] 207 | return dict 208 | 209 | def _load_sentence_word_indices(self): 210 | """ create dict of video sentence numbers with list of word indices as values 211 | 212 | :return: dict 213 | {v0: [i0, i1, i2], v1: [i0, i1, i2], ... ,} where v# is video number and 214 | i# is index to wordlist, ordered by sentence structure 215 | """ 216 | working_df = self.df.copy() 217 | working_df['idx'] = working_df.index 218 | working_df.sort_values(by='startframe', inplace=True) 219 | p = working_df.pivot('video', 'startframe', 'idx') 220 | p.fillna(-1, inplace=True) 221 | p = p.transpose() 222 | dict = {} 223 | for v in p: 224 | dict[v] = [int(i) for i in p[v] if i>=0] 225 | return dict 226 | 227 | def get_all_sequences(self): 228 | """ getter for entire db of items as series of sequences of feature lists for each frame 229 | 230 | :return: dict 231 | dictionary of lists of feature list sequence lists for each indexed item 232 | {3: [[[87, 225], [87, 225], ...], [[88, 219], [88, 219], ...]]], 233 | ...} 234 | """ 235 | return self._data 236 | 237 | def get_all_Xlengths(self): 238 | """ getter for entire db of items as (X, lengths) tuple for use with hmmlearn library 239 | 240 | :return: dict 241 | dictionary of (X, lengths) tuple, where X is a numpy array of feature lists and lengths is 242 | a list of lengths of sequences within X; should always have only one item in lengths 243 | {3: (array([[ 87, 225],[ 87, 225], ... [ 87, 225, 62, 127], [ 87, 225, 65, 128]]), [14]), 244 | ...} 245 | """ 246 | return self._hmm_data 247 | 248 | def get_item_sequences(self, item:int): 249 | """ getter for single item series of sequences of feature lists for each frame 250 | 251 | :param word: str 252 | :return: list 253 | lists of feature list sequence lists for given word 254 | [[[87, 225], [87, 225], ...]]] 255 | """ 256 | return self._data[item] 257 | 258 | def get_item_Xlengths(self, item:int): 259 | """ getter for single item (X, lengths) tuple for use with hmmlearn library 260 | 261 | :param word: 262 | :return: (list, list) 263 | (X, lengths) tuple, where X is a numpy array of feature lists and lengths is 264 | a list of lengths of sequences within X; lengths should always contain one item 265 | (array([[ 87, 225],[ 87, 225], ... [ 87, 225, 62, 127], [ 87, 225, 65, 128]]), [14]) 266 | """ 267 | return self._hmm_data[item] 268 | 269 | 270 | def combine_sequences(sequences): 271 | ''' 272 | concatenates sequences and return tuple of the new list and lengths 273 | :param sequences: 274 | :return: (list, list) 275 | ''' 276 | sequence_cat = [] 277 | sequence_lengths = [] 278 | # print("num of sequences in {} = {}".format(key, len(sequences))) 279 | for sequence in sequences: 280 | sequence_cat += sequence 281 | num_frames = len(sequence) 282 | sequence_lengths.append(num_frames) 283 | return sequence_cat, sequence_lengths 284 | 285 | def create_hmmlearn_data(dict): 286 | seq_len_dict = {} 287 | for key in dict: 288 | sequences = dict[key] 289 | sequence_cat, sequence_lengths = combine_sequences(sequences) 290 | seq_len_dict[key] = np.array(sequence_cat), sequence_lengths 291 | return seq_len_dict 292 | 293 | if __name__ == '__main__': 294 | asl= AslDb() 295 | print(asl.df.ix[98, 1]) 296 | 297 | 298 | -------------------------------------------------------------------------------- /isolation/isolation/isolation.py: -------------------------------------------------------------------------------- 1 | """ 2 | This file contains the `Board` class, which implements the rules for the 3 | game Isolation as described in lecture, modified so that the players move 4 | like knights in chess rather than queens. 5 | 6 | You MAY use and modify this class, however ALL function signatures must 7 | remain compatible with the defaults provided, and none of your changes will 8 | be available to project reviewers. 9 | """ 10 | import random 11 | import timeit 12 | from copy import copy 13 | 14 | TIME_LIMIT_MILLIS = 150 15 | 16 | 17 | class Board(object): 18 | """Implement a model for the game Isolation assuming each player moves like 19 | a knight in chess. 20 | 21 | Parameters 22 | ---------- 23 | player_1 : object 24 | An object with a get_move() function. This is the only function 25 | directly called by the Board class for each player. 26 | 27 | player_2 : object 28 | An object with a get_move() function. This is the only function 29 | directly called by the Board class for each player. 30 | 31 | width : int (optional) 32 | The number of columns that the board should have. 33 | 34 | height : int (optional) 35 | The number of rows that the board should have. 36 | """ 37 | BLANK = 0 38 | NOT_MOVED = None 39 | 40 | def __init__(self, player_1, player_2, width=7, height=7): 41 | self.width = width 42 | self.height = height 43 | self.move_count = 0 44 | self._player_1 = player_1 45 | self._player_2 = player_2 46 | self._active_player = player_1 47 | self._inactive_player = player_2 48 | 49 | # The last 3 entries of the board state includes initiative (0 for 50 | # player 1, 1 for player 2) player 2 last move, and player 1 last move 51 | self._board_state = [Board.BLANK] * (width * height + 3) 52 | self._board_state[-1] = Board.NOT_MOVED 53 | self._board_state[-2] = Board.NOT_MOVED 54 | 55 | def hash(self): 56 | return str(self._board_state).__hash__() 57 | 58 | @property 59 | def active_player(self): 60 | """The object registered as the player holding initiative in the 61 | current game state. 62 | """ 63 | return self._active_player 64 | 65 | @property 66 | def inactive_player(self): 67 | """The object registered as the player in waiting for the current 68 | game state. 69 | """ 70 | return self._inactive_player 71 | 72 | def get_opponent(self, player): 73 | """Return the opponent of the supplied player. 74 | 75 | Parameters 76 | ---------- 77 | player : object 78 | An object registered as a player in the current game. Raises an 79 | error if the supplied object is not registered as a player in 80 | this game. 81 | 82 | Returns 83 | ------- 84 | object 85 | The opponent of the input player object. 86 | """ 87 | if player == self._active_player: 88 | return self._inactive_player 89 | elif player == self._inactive_player: 90 | return self._active_player 91 | raise RuntimeError("`player` must be an object registered as a player in the current game.") 92 | 93 | def copy(self): 94 | """ Return a deep copy of the current board. """ 95 | new_board = Board(self._player_1, self._player_2, width=self.width, height=self.height) 96 | new_board.move_count = self.move_count 97 | new_board._active_player = self._active_player 98 | new_board._inactive_player = self._inactive_player 99 | new_board._board_state = copy(self._board_state) 100 | return new_board 101 | 102 | def forecast_move(self, move): 103 | """Return a deep copy of the current game with an input move applied to 104 | advance the game one ply. 105 | 106 | Parameters 107 | ---------- 108 | move : (int, int) 109 | A coordinate pair (row, column) indicating the next position for 110 | the active player on the board. 111 | 112 | Returns 113 | ------- 114 | isolation.Board 115 | A deep copy of the board with the input move applied. 116 | """ 117 | new_board = self.copy() 118 | new_board.apply_move(move) 119 | return new_board 120 | 121 | def move_is_legal(self, move): 122 | """Test whether a move is legal in the current game state. 123 | 124 | Parameters 125 | ---------- 126 | move : (int, int) 127 | A coordinate pair (row, column) indicating the next position for 128 | the active player on the board. 129 | 130 | Returns 131 | ------- 132 | bool 133 | Returns True if the move is legal, False otherwise 134 | """ 135 | idx = move[0] + move[1] * self.height 136 | return (0 <= move[0] < self.height and 0 <= move[1] < self.width and 137 | self._board_state[idx] == Board.BLANK) 138 | 139 | def get_blank_spaces(self): 140 | """Return a list of the locations that are still available on the board. 141 | """ 142 | return [(i, j) for j in range(self.width) for i in range(self.height) 143 | if self._board_state[i + j * self.height] == Board.BLANK] 144 | 145 | def get_player_location(self, player): 146 | """Find the current location of the specified player on the board. 147 | 148 | Parameters 149 | ---------- 150 | player : object 151 | An object registered as a player in the current game. 152 | 153 | Returns 154 | ------- 155 | (int, int) or None 156 | The coordinate pair (row, column) of the input player, or None 157 | if the player has not moved. 158 | """ 159 | if player == self._player_1: 160 | if self._board_state[-1] == Board.NOT_MOVED: 161 | return Board.NOT_MOVED 162 | idx = self._board_state[-1] 163 | elif player == self._player_2: 164 | if self._board_state[-2] == Board.NOT_MOVED: 165 | return Board.NOT_MOVED 166 | idx = self._board_state[-2] 167 | else: 168 | raise RuntimeError( 169 | "Invalid player in get_player_location: {}".format(player)) 170 | w = idx // self.height 171 | h = idx % self.height 172 | return (h, w) 173 | 174 | def get_legal_moves(self, player=None): 175 | """Return the list of all legal moves for the specified player. 176 | 177 | Parameters 178 | ---------- 179 | player : object (optional) 180 | An object registered as a player in the current game. If None, 181 | return the legal moves for the active player on the board. 182 | 183 | Returns 184 | ------- 185 | list<(int, int)> 186 | The list of coordinate pairs (row, column) of all legal moves 187 | for the player constrained by the current game state. 188 | """ 189 | if player is None: 190 | player = self.active_player 191 | return self.__get_moves(self.get_player_location(player)) 192 | 193 | def apply_move(self, move): 194 | """Move the active player to a specified location. 195 | 196 | Parameters 197 | ---------- 198 | move : (int, int) 199 | A coordinate pair (row, column) indicating the next position for 200 | the active player on the board. 201 | """ 202 | idx = move[0] + move[1] * self.height 203 | last_move_idx = int(self.active_player == self._player_2) + 1 204 | self._board_state[-last_move_idx] = idx 205 | self._board_state[idx] = 1 206 | self._board_state[-3] ^= 1 207 | self._active_player, self._inactive_player = self._inactive_player, self._active_player 208 | self.move_count += 1 209 | 210 | def is_winner(self, player): 211 | """ Test whether the specified player has won the game. """ 212 | return player == self._inactive_player and not self.get_legal_moves(self._active_player) 213 | 214 | def is_loser(self, player): 215 | """ Test whether the specified player has lost the game. """ 216 | return player == self._active_player and not self.get_legal_moves(self._active_player) 217 | 218 | def utility(self, player): 219 | """Returns the utility of the current game state from the perspective 220 | of the specified player. 221 | 222 | / +infinity, "player" wins 223 | utility = | -infinity, "player" loses 224 | \ 0, otherwise 225 | 226 | Parameters 227 | ---------- 228 | player : object (optional) 229 | An object registered as a player in the current game. If None, 230 | return the utility for the active player on the board. 231 | 232 | Returns 233 | ---------- 234 | float 235 | The utility value of the current game state for the specified 236 | player. The game has a utility of +inf if the player has won, 237 | a value of -inf if the player has lost, and a value of 0 238 | otherwise. 239 | """ 240 | if not self.get_legal_moves(self._active_player): 241 | 242 | if player == self._inactive_player: 243 | return float("inf") 244 | 245 | if player == self._active_player: 246 | return float("-inf") 247 | 248 | return 0. 249 | 250 | def __get_moves(self, loc): 251 | """Generate the list of possible moves for an L-shaped motion (like a 252 | knight in chess). 253 | """ 254 | if loc == Board.NOT_MOVED: 255 | return self.get_blank_spaces() 256 | 257 | r, c = loc 258 | directions = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), 259 | (1, -2), (1, 2), (2, -1), (2, 1)] 260 | valid_moves = [(r + dr, c + dc) for dr, dc in directions 261 | if self.move_is_legal((r + dr, c + dc))] 262 | random.shuffle(valid_moves) 263 | return valid_moves 264 | 265 | def print_board(self): 266 | """DEPRECATED - use Board.to_string()""" 267 | return self.to_string() 268 | 269 | def to_string(self, symbols=['1', '2']): 270 | """Generate a string representation of the current game state, marking 271 | the location of each player and indicating which cells have been 272 | blocked, and which remain open. 273 | """ 274 | p1_loc = self._board_state[-1] 275 | p2_loc = self._board_state[-2] 276 | 277 | col_margin = len(str(self.height - 1)) + 1 278 | prefix = "{:<" + "{}".format(col_margin) + "}" 279 | offset = " " * (col_margin + 3) 280 | out = offset + ' '.join(map(str, range(self.width))) + '\n\r' 281 | for i in range(self.height): 282 | out += prefix.format(i) + ' | ' 283 | for j in range(self.width): 284 | idx = i + j * self.height 285 | if not self._board_state[idx]: 286 | out += ' ' 287 | elif p1_loc == idx: 288 | out += symbols[0] 289 | elif p2_loc == idx: 290 | out += symbols[1] 291 | else: 292 | out += '-' 293 | out += ' | ' 294 | out += '\n\r' 295 | 296 | return out 297 | 298 | def play(self, time_limit=TIME_LIMIT_MILLIS): 299 | """Execute a match between the players by alternately soliciting them 300 | to select a move and applying it in the game. 301 | 302 | Parameters 303 | ---------- 304 | time_limit : numeric (optional) 305 | The maximum number of milliseconds to allow before timeout 306 | during each turn. 307 | 308 | Returns 309 | ---------- 310 | (player, list<[(int, int),]>, str) 311 | Return multiple including the winning player, the complete game 312 | move history, and a string indicating the reason for losing 313 | (e.g., timeout or invalid move). 314 | """ 315 | move_history = [] 316 | 317 | time_millis = lambda: 1000 * timeit.default_timer() 318 | 319 | while True: 320 | 321 | legal_player_moves = self.get_legal_moves() 322 | game_copy = self.copy() 323 | 324 | move_start = time_millis() 325 | time_left = lambda : time_limit - (time_millis() - move_start) 326 | curr_move = self._active_player.get_move(game_copy, time_left) 327 | move_end = time_left() 328 | 329 | if curr_move is None: 330 | curr_move = Board.NOT_MOVED 331 | 332 | if move_end < 0: 333 | return self._inactive_player, move_history, "timeout" 334 | 335 | if curr_move not in legal_player_moves: 336 | if len(legal_player_moves) > 0: 337 | return self._inactive_player, move_history, "forfeit" 338 | return self._inactive_player, move_history, "illegal move" 339 | 340 | move_history.append(list(curr_move)) 341 | 342 | self.apply_move(curr_move) 343 | -------------------------------------------------------------------------------- /planning/my_air_cargo_problems.py: -------------------------------------------------------------------------------- 1 | from aimacode.logic import PropKB 2 | from aimacode.planning import Action 3 | from aimacode.search import ( 4 | Node, Problem, 5 | ) 6 | from aimacode.utils import expr 7 | from lp_utils import ( 8 | FluentState, encode_state, decode_state, 9 | ) 10 | from my_planning_graph import PlanningGraph 11 | 12 | from functools import lru_cache 13 | 14 | 15 | class AirCargoProblem(Problem): 16 | def __init__(self, cargos, planes, airports, initial: FluentState, goal: list): 17 | """ 18 | 19 | :param cargos: list of str 20 | cargos in the problem 21 | :param planes: list of str 22 | planes in the problem 23 | :param airports: list of str 24 | airports in the problem 25 | :param initial: FluentState object 26 | positive and negative literal fluents (as expr) describing initial state 27 | :param goal: list of expr 28 | literal fluents required for goal test 29 | """ 30 | self.state_map = initial.pos + initial.neg 31 | self.initial_state_TF = encode_state(initial, self.state_map) 32 | Problem.__init__(self, self.initial_state_TF, goal=goal) 33 | self.cargos = cargos 34 | self.planes = planes 35 | self.airports = airports 36 | self.actions_list = self.get_actions() 37 | 38 | def get_actions(self): 39 | """ 40 | This method creates concrete actions (no variables) for all actions in the problem 41 | domain action schema and turns them into complete Action objects as defined in the 42 | aimacode.planning module. It is computationally expensive to call this method directly; 43 | however, it is called in the constructor and the results cached in the `actions_list` property. 44 | 45 | Returns: 46 | ---------- 47 | list 48 | list of Action objects 49 | """ 50 | 51 | 52 | # concrete actions definition: specific literal action that does not include variables as with the schema 53 | # for example, the action schema 'Load(c, p, a)' can represent the concrete actions 'Load(C1, P1, SFO)' 54 | # or 'Load(C2, P2, JFK)'. The actions for the planning problem must be concrete because the problems in 55 | # forward search and Planning Graphs must use Propositional Logic 56 | 57 | def load_actions(): 58 | """Create all concrete Load actions and return a list 59 | 60 | :return: list of Action objects 61 | """ 62 | loads = [] 63 | 64 | for c in self.cargos: 65 | for p in self.planes: 66 | for a in self.airports: 67 | precond_pos = [expr("At({}, {})".format(c, a)), 68 | expr("At({}, {})".format(p, a))] 69 | precond_neg = [] 70 | effect_add = [expr("In({}, {})".format(c, p))] 71 | effect_rem = [expr("At({}, {})".format(c, a))] 72 | load = Action(expr("Load({}, {}, {})".format(c, p, a)), 73 | [precond_pos, precond_neg], 74 | [effect_add, effect_rem]) 75 | loads.append(load) 76 | 77 | return loads 78 | 79 | def unload_actions(): 80 | """Create all concrete Unload actions and return a list 81 | 82 | :return: list of Action objects 83 | """ 84 | unloads = [] 85 | 86 | for c in self.cargos: 87 | for p in self.planes: 88 | for a in self.airports: 89 | precond_pos = [expr("In({}, {})".format(c, p)), 90 | expr("At({}, {})".format(p, a))] 91 | precond_neg = [] 92 | effect_add = [expr("At({}, {})".format(c, a))] 93 | effect_rem = [expr("In({}, {})".format(c, p))] 94 | unload = Action(expr("Unload({}, {}, {})".format(c, p, a)), 95 | [precond_pos, precond_neg], 96 | [effect_add, effect_rem]) 97 | unloads.append(unload) 98 | 99 | return unloads 100 | 101 | def fly_actions(): 102 | """Create all concrete Fly actions and return a list 103 | 104 | :return: list of Action objects 105 | """ 106 | flys = [] 107 | for fr in self.airports: 108 | for to in self.airports: 109 | if fr != to: 110 | for p in self.planes: 111 | precond_pos = [expr("At({}, {})".format(p, fr)), 112 | ] 113 | precond_neg = [] 114 | effect_add = [expr("At({}, {})".format(p, to))] 115 | effect_rem = [expr("At({}, {})".format(p, fr))] 116 | fly = Action(expr("Fly({}, {}, {})".format(p, fr, to)), 117 | [precond_pos, precond_neg], 118 | [effect_add, effect_rem]) 119 | flys.append(fly) 120 | return flys 121 | 122 | return load_actions() + unload_actions() + fly_actions() 123 | 124 | def actions(self, state: str) -> list: 125 | """ Return the actions that can be executed in the given state. 126 | 127 | :param state: str 128 | state represented as T/F string of mapped fluents (state variables) 129 | e.g. 'FTTTFF' 130 | :return: list of Action objects 131 | """ 132 | # initial state ∧ all possible action descriptions ∧ goal 133 | # here we find all possible action based on precondition axioms 134 | 135 | possible_actions = [] 136 | kb = PropKB() 137 | kb.tell(decode_state(state, self.state_map).pos_sentence()) 138 | for action in self.actions_list: 139 | is_possible = True 140 | for clause in action.precond_pos: 141 | if clause not in kb.clauses: 142 | is_possible = False 143 | break 144 | for clause in action.precond_neg: 145 | if clause in kb.clauses: 146 | is_possible = False 147 | break 148 | if is_possible: 149 | possible_actions.append(action) 150 | return possible_actions 151 | 152 | def result(self, state: str, action: Action): 153 | """ Return the state that results from executing the given 154 | action in the given state. The action must be one of 155 | self.actions(state). 156 | 157 | :param state: state entering node 158 | :param action: Action applied 159 | :return: resulting state after action 160 | """ 161 | 162 | new_state = FluentState([], []) 163 | cur_state = decode_state(state, self.state_map) 164 | 165 | for fluent in cur_state.pos: 166 | if fluent not in action.effect_rem: 167 | new_state.pos.append(fluent) 168 | for fluent in cur_state.neg: 169 | if fluent not in action.effect_add: 170 | new_state.neg.append(fluent) 171 | for fluent in action.effect_add: 172 | if fluent not in new_state.pos: 173 | new_state.pos.append(fluent) 174 | for fluent in action.effect_rem: 175 | if fluent not in new_state.neg: 176 | new_state.neg.append(fluent) 177 | return encode_state(new_state, self.state_map) 178 | 179 | def goal_test(self, state: str) -> bool: 180 | """ Test the state to see if goal is reached 181 | 182 | :param state: str representing state 183 | :return: bool 184 | """ 185 | kb = PropKB() 186 | kb.tell(decode_state(state, self.state_map).pos_sentence()) 187 | for clause in self.goal: 188 | if clause not in kb.clauses: 189 | return False 190 | return True 191 | 192 | def h_1(self, node: Node): 193 | # note that this is not a true heuristic 194 | h_const = 1 195 | return h_const 196 | 197 | @lru_cache(maxsize=8192) 198 | def h_pg_levelsum(self, node: Node): 199 | """This heuristic uses a planning graph representation of the problem 200 | state space to estimate the sum of all actions that must be carried 201 | out from the current state in order to satisfy each individual goal 202 | condition. 203 | """ 204 | # requires implemented PlanningGraph class 205 | pg = PlanningGraph(self, node.state) 206 | pg_levelsum = pg.h_levelsum() 207 | return pg_levelsum 208 | 209 | @lru_cache(maxsize=8192) 210 | def h_ignore_preconditions(self, node: Node): 211 | """This heuristic estimates the minimum number of actions that must be 212 | carried out from the current state in order to satisfy all of the goal 213 | conditions by ignoring the preconditions required for an action to be 214 | executed. 215 | """ 216 | 217 | count = 0 218 | kb = PropKB() 219 | kb.tell((decode_state(node.state, self.state_map).pos_sentence())) 220 | 221 | for clause in self.goal: 222 | if clause not in kb.clauses: 223 | count += 1 224 | return count 225 | 226 | 227 | def air_cargo_p1() -> AirCargoProblem: 228 | """ 229 | Init(At(C1, SFO) ∧ At(C2, JFK) 230 | ∧ At(P1, SFO) ∧ At(P2, JFK) 231 | ∧ Cargo(C1) ∧ Cargo(C2) 232 | ∧ Plane(P1) ∧ Plane(P2) 233 | ∧ Airport(JFK) ∧ Airport(SFO)) 234 | Goal(At(C1, JFK) ∧ At(C2, SFO)) 235 | """ 236 | 237 | cargos = ['C1', 'C2'] 238 | planes = ['P1', 'P2'] 239 | airports = ['JFK', 'SFO'] 240 | pos = [expr('At(C1, SFO)'), 241 | expr('At(C2, JFK)'), 242 | expr('At(P1, SFO)'), 243 | expr('At(P2, JFK)'), 244 | ] 245 | neg = [expr('At(C2, SFO)'), 246 | expr('In(C2, P1)'), 247 | expr('In(C2, P2)'), 248 | expr('At(C1, JFK)'), 249 | expr('In(C1, P1)'), 250 | expr('In(C1, P2)'), 251 | expr('At(P1, JFK)'), 252 | expr('At(P2, SFO)'), 253 | ] 254 | init = FluentState(pos, neg) 255 | goal = [expr('At(C1, JFK)'), 256 | expr('At(C2, SFO)'), 257 | ] 258 | return AirCargoProblem(cargos, planes, airports, init, goal) 259 | 260 | 261 | def air_cargo_p2() -> AirCargoProblem: 262 | 263 | """ 264 | Problem 2 initial state and goal: 265 | Init(At(C1, SFO) ∧ At(C2, JFK) ∧ At(C3, ATL) 266 | ∧ At(P1, SFO) ∧ At(P2, JFK) ∧ At(P3, ATL) 267 | ∧ Cargo(C1) ∧ Cargo(C2) ∧ Cargo(C3) 268 | ∧ Plane(P1) ∧ Plane(P2) ∧ Plane(P3) 269 | ∧ Airport(JFK) ∧ Airport(SFO) ∧ Airport(ATL)) 270 | Goal(At(C1, JFK) ∧ At(C2, SFO) ∧ At(C3, SFO)) 271 | """ 272 | 273 | cargos = ['C1', 'C2', 'C3'] 274 | planes = ['P1', 'P2', 'P3'] 275 | airports = ['ATL', 'JFK', 'SFO'] 276 | 277 | pos = [expr('At(C1, SFO)'), 278 | expr('At(C2, JFK)'), 279 | expr('At(C3, ATL)'), 280 | expr('At(P1, SFO)'), 281 | expr('At(P2, JFK)'), 282 | expr('At(P3, ATL)'), 283 | ] 284 | 285 | neg = [expr('At(C1, ATL)'), 286 | expr('At(C1, JFK)'), 287 | expr('In(C1, P1)'), 288 | expr('In(C1, P2)'), 289 | expr('In(C1, P3)'), 290 | expr('At(C2, ATL)'), 291 | expr('At(C2, SFO)'), 292 | expr('In(C2, P1)'), 293 | expr('In(C2, P2)'), 294 | expr('In(C2, P3)'), 295 | expr('At(C3, JFK)'), 296 | expr('At(C3, SFO)'), 297 | expr('At(P1, ATL)'), 298 | expr('At(P1, JFK)'), 299 | expr('At(P2, ATL)'), 300 | expr('At(P2, SFO)'), 301 | expr('At(P3, JFK)'), 302 | expr('At(P3, SFO)'), 303 | expr('In(C3, P1)'), 304 | expr('In(C3, P2)'), 305 | expr('In(C3, P3)'), 306 | ] 307 | init = FluentState(pos, neg) 308 | 309 | goal = [expr('At(C1, JFK)'), 310 | expr('At(C2, SFO)'), 311 | expr('At(C3, SFO)'), 312 | ] 313 | return AirCargoProblem(cargos, planes, airports, init, goal) 314 | 315 | 316 | def air_cargo_p3() -> AirCargoProblem: 317 | """ 318 | Init(At(C1, SFO) ∧ At(C2, JFK) ∧ At(C3, ATL) ∧ At(C4, ORD) 319 | ∧ At(P1, SFO) ∧ At(P2, JFK) 320 | ∧ Cargo(C1) ∧ Cargo(C2) ∧ Cargo(C3) ∧ Cargo(C4) 321 | ∧ Plane(P1) ∧ Plane(P2) 322 | ∧ Airport(JFK) ∧ Airport(SFO) ∧ Airport(ATL) ∧ Airport(ORD)) 323 | Goal(At(C1, JFK) ∧ At(C3, JFK) ∧ At(C2, SFO) ∧ At(C4, SFO)) 324 | """ 325 | cargos = ['C1', 'C2', 'C3', 'C4'] 326 | planes = ['P1', 'P2'] 327 | airports = ['ATL', 'JFK', 'ORD', 'SFO'] 328 | 329 | pos = [expr('At(C1, SFO)'), 330 | expr('At(C2, JFK)'), 331 | expr('At(C3, ATL)'), 332 | expr('At(C4, ORD)'), 333 | expr('At(P1, SFO)'), 334 | expr('At(P2, JFK)'), 335 | ] 336 | 337 | neg = [expr('At(C1, ATL)'), 338 | expr('At(C1, JFK)'), 339 | expr('At(C1, ORD)'), 340 | expr('In(C1, P1)'), 341 | expr('In(C1, P2)'), 342 | expr('At(C2, ATL)'), 343 | expr('At(C2, ORD)'), 344 | expr('At(C2, SFO)'), 345 | expr('In(C2, P1)'), 346 | expr('In(C2, P2)'), 347 | expr('At(C3, JFK)'), 348 | expr('At(C3, ORD)'), 349 | expr('At(C3, SFO)'), 350 | expr('In(C3, P1)'), 351 | expr('In(C3, P2)'), 352 | expr('At(C4, ATL)'), 353 | expr('At(C4, JFK)'), 354 | expr('At(C4, SFO)'), 355 | expr('In(C4, P1)'), 356 | expr('In(C4, P2)'), 357 | expr('At(P1, ATL)'), 358 | expr('At(P1, JFK)'), 359 | expr('At(P1, ORD)'), 360 | expr('At(P2, ATL)'), 361 | expr('At(P2, ORD)'), 362 | expr('At(P2, SFO)') 363 | ] 364 | init = FluentState(pos, neg) 365 | 366 | goal = [expr('At(C1, JFK)'), 367 | expr('At(C2, SFO)'), 368 | expr('At(C3, JFK)'), 369 | expr('At(C4, SFO)'), 370 | ] 371 | return AirCargoProblem(cargos, planes, airports, init, goal) -------------------------------------------------------------------------------- /planning/aimacode/search.py: -------------------------------------------------------------------------------- 1 | """Search (Chapters 3-4) 2 | 3 | The way to use this code is to subclass Problem to create a class of problems, 4 | then create problem instances and solve them with calls to the various search 5 | functions.""" 6 | 7 | from .utils import ( 8 | is_in, memoize, print_table, Stack, FIFOQueue, PriorityQueue, name 9 | ) 10 | 11 | import sys 12 | 13 | infinity = float('inf') 14 | 15 | # ______________________________________________________________________________ 16 | 17 | 18 | class Problem: 19 | 20 | """The abstract class for a formal problem. You should subclass 21 | this and implement the methods actions and result, and possibly 22 | __init__, goal_test, and path_cost. Then you will create instances 23 | of your subclass and solve them with the various search functions.""" 24 | 25 | def __init__(self, initial, goal=None): 26 | """The constructor specifies the initial state, and possibly a goal 27 | state, if there is a unique goal. Your subclass's constructor can add 28 | other arguments.""" 29 | self.initial = initial 30 | self.goal = goal 31 | 32 | def actions(self, state): 33 | """Return the actions that can be executed in the given 34 | state. The result would typically be a list, but if there are 35 | many actions, consider yielding them one at a time in an 36 | iterator, rather than building them all at once.""" 37 | raise NotImplementedError 38 | 39 | def result(self, state, action): 40 | """Return the state that results from executing the given 41 | action in the given state. The action must be one of 42 | self.actions(state).""" 43 | raise NotImplementedError 44 | 45 | def goal_test(self, state): 46 | """Return True if the state is a goal. The default method compares the 47 | state to self.goal or checks for state in self.goal if it is a 48 | list, as specified in the constructor. Override this method if 49 | checking against a single self.goal is not enough.""" 50 | if isinstance(self.goal, list): 51 | return is_in(state, self.goal) 52 | else: 53 | return state == self.goal 54 | 55 | def path_cost(self, c, state1, action, state2): 56 | """Return the cost of a solution path that arrives at state2 from 57 | state1 via action, assuming cost c to get up to state1. If the problem 58 | is such that the path doesn't matter, this function will only look at 59 | state2. If the path does matter, it will consider c and maybe state1 60 | and action. The default method costs 1 for every step in the path.""" 61 | return c + 1 62 | 63 | def value(self, state): 64 | """For optimization problems, each state has a value. Hill-climbing 65 | and related algorithms try to maximize this value.""" 66 | raise NotImplementedError 67 | # ______________________________________________________________________________ 68 | 69 | 70 | class Node: 71 | 72 | """A node in a search tree. Contains a pointer to the parent (the node 73 | that this is a successor of) and to the actual state for this node. Note 74 | that if a state is arrived at by two paths, then there are two nodes with 75 | the same state. Also includes the action that got us to this state, and 76 | the total path_cost (also known as g) to reach the node. Other functions 77 | may add an f and h value; see best_first_graph_search and astar_search for 78 | an explanation of how the f and h values are handled. You will not need to 79 | subclass this class.""" 80 | 81 | def __init__(self, state, parent=None, action=None, path_cost=0): 82 | "Create a search tree Node, derived from a parent by an action." 83 | self.state = state 84 | self.parent = parent 85 | self.action = action 86 | self.path_cost = path_cost 87 | self.depth = 0 88 | if parent: 89 | self.depth = parent.depth + 1 90 | 91 | def __repr__(self): 92 | return "" % (self.state,) 93 | 94 | def __lt__(self, node): 95 | return self.state < node.state 96 | 97 | def expand(self, problem): 98 | "List the nodes reachable in one step from this node." 99 | return [self.child_node(problem, action) 100 | for action in problem.actions(self.state)] 101 | 102 | def child_node(self, problem, action): 103 | "[Figure 3.10]" 104 | next = problem.result(self.state, action) 105 | return Node(next, self, action, 106 | problem.path_cost(self.path_cost, self.state, 107 | action, next)) 108 | 109 | def solution(self): 110 | "Return the sequence of actions to go from the root to this node." 111 | return [node.action for node in self.path()[1:]] 112 | 113 | def path(self): 114 | "Return a list of nodes forming the path from the root to this node." 115 | node, path_back = self, [] 116 | while node: 117 | path_back.append(node) 118 | node = node.parent 119 | return list(reversed(path_back)) 120 | 121 | # We want for a queue of nodes in breadth_first_search or 122 | # astar_search to have no duplicated states, so we treat nodes 123 | # with the same state as equal. [Problem: this may not be what you 124 | # want in other contexts.] 125 | 126 | def __eq__(self, other): 127 | return isinstance(other, Node) and self.state == other.state 128 | 129 | def __hash__(self): 130 | return hash(self.state) 131 | 132 | # ______________________________________________________________________________ 133 | # Uninformed Search algorithms 134 | 135 | 136 | def tree_search(problem, frontier): 137 | """Search through the successors of a problem to find a goal. 138 | The argument frontier should be an empty queue. 139 | Don't worry about repeated paths to a state. [Figure 3.7]""" 140 | frontier.append(Node(problem.initial)) 141 | while frontier: 142 | node = frontier.pop() 143 | if problem.goal_test(node.state): 144 | return node 145 | frontier.extend(node.expand(problem)) 146 | return None 147 | 148 | 149 | def graph_search(problem, frontier): 150 | """Search through the successors of a problem to find a goal. 151 | The argument frontier should be an empty queue. 152 | If two paths reach a state, only use the first one. [Figure 3.7]""" 153 | frontier.append(Node(problem.initial)) 154 | explored = set() 155 | while frontier: 156 | node = frontier.pop() 157 | if problem.goal_test(node.state): 158 | return node 159 | explored.add(node.state) 160 | frontier.extend(child for child in node.expand(problem) 161 | if child.state not in explored and 162 | child not in frontier) 163 | return None 164 | 165 | 166 | def breadth_first_tree_search(problem): 167 | "Search the shallowest nodes in the search tree first." 168 | return tree_search(problem, FIFOQueue()) 169 | 170 | 171 | def depth_first_tree_search(problem): 172 | "Search the deepest nodes in the search tree first." 173 | return tree_search(problem, Stack()) 174 | 175 | 176 | def depth_first_graph_search(problem): 177 | "Search the deepest nodes in the search tree first." 178 | return graph_search(problem, Stack()) 179 | 180 | 181 | def breadth_first_search(problem): 182 | "[Figure 3.11]" 183 | node = Node(problem.initial) 184 | if problem.goal_test(node.state): 185 | return node 186 | frontier = FIFOQueue() 187 | frontier.append(node) 188 | explored = set() 189 | while frontier: 190 | node = frontier.pop() 191 | explored.add(node.state) 192 | for child in node.expand(problem): 193 | if child.state not in explored and child not in frontier: 194 | if problem.goal_test(child.state): 195 | return child 196 | frontier.append(child) 197 | return None 198 | 199 | 200 | def best_first_graph_search(problem, f): 201 | """Search the nodes with the lowest f scores first. 202 | You specify the function f(node) that you want to minimize; for example, 203 | if f is a heuristic estimate to the goal, then we have greedy best 204 | first search; if f is node.depth then we have breadth-first search. 205 | There is a subtlety: the line "f = memoize(f, 'f')" means that the f 206 | values will be cached on the nodes as they are computed. So after doing 207 | a best first search you can examine the f values of the path returned.""" 208 | f = memoize(f, 'f') 209 | node = Node(problem.initial) 210 | if problem.goal_test(node.state): 211 | return node 212 | frontier = PriorityQueue(min, f) 213 | frontier.append(node) 214 | explored = set() 215 | while frontier: 216 | node = frontier.pop() 217 | if problem.goal_test(node.state): 218 | return node 219 | explored.add(node.state) 220 | for child in node.expand(problem): 221 | if child.state not in explored and child not in frontier: 222 | frontier.append(child) 223 | elif child in frontier: 224 | incumbent = frontier[child] 225 | if f(child) < f(incumbent): 226 | # del frontier[incumbent] 227 | frontier.append(child) 228 | return None 229 | 230 | 231 | def uniform_cost_search(problem): 232 | "[Figure 3.14]" 233 | return best_first_graph_search(problem, lambda node: node.path_cost) 234 | 235 | 236 | def depth_limited_search(problem, limit=50): 237 | "[Figure 3.17]" 238 | def recursive_dls(node, problem, limit): 239 | if problem.goal_test(node.state): 240 | return node 241 | elif limit == 0: 242 | return 'cutoff' 243 | else: 244 | cutoff_occurred = False 245 | for child in node.expand(problem): 246 | result = recursive_dls(child, problem, limit - 1) 247 | if result == 'cutoff': 248 | cutoff_occurred = True 249 | elif result is not None: 250 | return result 251 | return 'cutoff' if cutoff_occurred else None 252 | 253 | # Body of depth_limited_search: 254 | return recursive_dls(Node(problem.initial), problem, limit) 255 | 256 | 257 | def iterative_deepening_search(problem): 258 | "[Figure 3.18]" 259 | for depth in range(sys.maxsize): 260 | result = depth_limited_search(problem, depth) 261 | if result != 'cutoff': 262 | return result 263 | 264 | # ______________________________________________________________________________ 265 | # Informed (Heuristic) Search 266 | 267 | greedy_best_first_graph_search = best_first_graph_search 268 | # Greedy best-first search is accomplished by specifying f(n) = h(n). 269 | 270 | 271 | def astar_search(problem, h=None): 272 | """A* search is best-first graph search with f(n) = g(n)+h(n). 273 | You need to specify the h function when you call astar_search, or 274 | else in your Problem subclass.""" 275 | h = memoize(h or problem.h, 'h') 276 | return best_first_graph_search(problem, lambda n: n.path_cost + h(n)) 277 | 278 | # ______________________________________________________________________________ 279 | # Other search algorithms 280 | 281 | 282 | def recursive_best_first_search(problem, h=None): 283 | "[Figure 3.26]" 284 | h = memoize(h or problem.h, 'h') 285 | 286 | def RBFS(problem, node, flimit): 287 | if problem.goal_test(node.state): 288 | return node, 0 # (The second value is immaterial) 289 | successors = node.expand(problem) 290 | if len(successors) == 0: 291 | return None, infinity 292 | for s in successors: 293 | s.f = max(s.path_cost + h(s), node.f) 294 | while True: 295 | # Order by lowest f value 296 | successors.sort(key=lambda x: x.f) 297 | best = successors[0] 298 | if best.f > flimit: 299 | return None, best.f 300 | if len(successors) > 1: 301 | alternative = successors[1].f 302 | else: 303 | alternative = infinity 304 | result, best.f = RBFS(problem, best, min(flimit, alternative)) 305 | if result is not None: 306 | return result, best.f 307 | 308 | node = Node(problem.initial) 309 | node.f = h(node) 310 | result, bestf = RBFS(problem, node, infinity) 311 | return result 312 | 313 | # ______________________________________________________________________________ 314 | 315 | # Code to compare searchers on various problems. 316 | 317 | 318 | class InstrumentedProblem(Problem): 319 | 320 | """Delegates to a problem, and keeps statistics.""" 321 | 322 | def __init__(self, problem): 323 | self.problem = problem 324 | self.succs = self.goal_tests = self.states = 0 325 | self.found = None 326 | 327 | def actions(self, state): 328 | self.succs += 1 329 | return self.problem.actions(state) 330 | 331 | def result(self, state, action): 332 | self.states += 1 333 | return self.problem.result(state, action) 334 | 335 | def goal_test(self, state): 336 | self.goal_tests += 1 337 | result = self.problem.goal_test(state) 338 | if result: 339 | self.found = state 340 | return result 341 | 342 | def path_cost(self, c, state1, action, state2): 343 | return self.problem.path_cost(c, state1, action, state2) 344 | 345 | def value(self, state): 346 | return self.problem.value(state) 347 | 348 | def __getattr__(self, attr): 349 | return getattr(self.problem, attr) 350 | 351 | def __repr__(self): 352 | return '<%4d/%4d/%4d/%s>' % (self.succs, self.goal_tests, 353 | self.states, str(self.found)[:4]) 354 | 355 | 356 | def compare_searchers(problems, header, 357 | searchers=[breadth_first_tree_search, 358 | breadth_first_search, 359 | depth_first_graph_search, 360 | iterative_deepening_search, 361 | depth_limited_search, 362 | recursive_best_first_search]): 363 | def do(searcher, problem): 364 | p = InstrumentedProblem(problem) 365 | searcher(p) 366 | return p 367 | table = [[name(s)] + [do(s, p) for p in problems] for s in searchers] 368 | print_table(table, header) 369 | --------------------------------------------------------------------------------