diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..c4f3830d --- /dev/null +++ b/.gitignore @@ -0,0 +1,97 @@ + +# Created by https://www.gitignore.io/api/python + +### Python ### +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*,cover +.hypothesis/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# dotenv +.env + +# virtualenv +.venv/ +venv/ +ENV/ + +# Spyder project settings +.spyderproject + +# Rope project settings +.ropeproject + +# End of https://www.gitignore.io/api/python diff --git a/.udacity-pa/projects.py b/.udacity-pa/projects.py new file mode 100644 index 00000000..d25abb4a --- /dev/null +++ b/.udacity-pa/projects.py @@ -0,0 +1,98 @@ +import os + +from udacity_pa import udacity + +from glob import glob + +SETTINGS = { + "isolation": { + "required": { + "game_agent": {"ext": ["py"], "size": 0.2}, + }, + "optional": { + "heuristic_analysis": {"ext": ["pdf"], "size": 6.0}, + "research_review": {"ext": ["pdf"], "size": 6.0}, + } + }, + "isolation-pvp": { + "required": { + "competition_agent": {"ext": ["py"], "size": 0.2}, + }, + "optional": { + "data": {"ext": ["json"], "size": 4} + } + } +} +nanodegree = "nd889" +projects = list(SETTINGS.keys()) + + +RESUBMIT_MSG = """ +NOTICE: + + You are about to submit your agent. If you have previously submitted + your agent, continuing will replace that submission. + +""" + + +def require_confirmation(text): + print(text) + ans = input("Please type 'yes' to confirm submission>") + print() + if ans.lower() != "yes": + print( + " Submission aborted -- you must confirm submission to proceed.\n" + ) + exit() + + +def validate_file_info(pattern, lo=1, hi=1, size=6, ext=[]): + + filenames = [x for x in glob(pattern + ".*") + if not ext or str.lower(os.path.splitext(x)[-1])[1:] in ext] + + if not (lo <= len(filenames) <= hi): + raise RuntimeError( + ("Submission Failed - a required file was missing. At least " + + "{!s} file(s) and no more than {!s} file(s) that match the " + + "pattern '{!s}.' with one of the extensions: {!s} must " + + "be found in the current working directory.") + .format(lo, hi, pattern, ext)) + + large_files = [x for x in filenames if os.stat(x).st_size > size * 2**20] + if large_files: + raise RuntimeError( + ("Submission Failed: One or more files is too large. Please " + + "make sure that the following files are under {!s}MB and try " + + "again: {!s}").format(size, large_files)) + + return filenames + + +def submit(options): + + if len(options.args) == 0 or options.args[0] not in projects: + raise RuntimeError("You must specifiy 'isolation' or 'isolation-pvp' after 'udacity submit'.") + + project_name = options.args[0] + if project_name == 'isolation-pvp': + require_confirmation(RESUBMIT_MSG) + patterns = SETTINGS.get(project_name, {}) + if not patterns: + raise RuntimeError("") + + required_files = [validate_file_info(ptn, **kwargs) + for ptn, kwargs in patterns.get("required", {}).items()] + optional_files = [validate_file_info(ptn, lo=0, **kwargs) + for ptn, kwargs in patterns.get("optional", {}).items()] + filenames = sum(required_files + optional_files, []) + file_info = (list(patterns.get("required", {}).values()) + + list(patterns.get("optional", {}).values())) + max_size = sum([dict(info).get("size", 0) for info in file_info]) * 2**20 + + udacity.submit(nanodegree, + project_name, + filenames, + environment = options.environment, + max_zip_size=max_size) diff --git a/LICENSE b/LICENSE new file mode 100644 index 00000000..a5e525f9 --- /dev/null +++ b/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2017 Udacity, Inc. + +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: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/README.md b/README.md index c7027532..975d71cd 100644 --- a/README.md +++ b/README.md @@ -1,24 +1,44 @@ # Build a Game-playing Agent -## Synopsis +![Example game of isolation](viz.gif) -In this project, students will develop an adversarial search agent to play the game "Isolation". Students only need to modify code in the `game_agent.py`, however, code is included for example player and evaluation functions for you to review and test against in the other files. +## Synopsis -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. +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. 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). 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. -These rules are implemented in the `isolation.Board` class provided in the repository. +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. + + +## Instructions + +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. + +Students must implement the following functions: +- `MinimaxPlayer.minimax()`: implement minimax search +- `AlphaBetaPlayer.alphabeta()`: implement minimax search with alpha-beta pruning +- `AlphaBetaPlayer.get_move()`: implement iterative deepening search +- `custom_score()`: implement your own best position evaluation heuristic +- `custom_score_2()`: implement your own alternate position evaluation heuristic +- `custom_score_3()`: implement your own alternate position evaluation heuristic -## Quickstart Guide +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. -The following example creates a game and illustrates the basic API. You can run this example with `python sample_players.py` +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`.) + + +### Quickstart Guide + +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` from isolation import Board + from sample_players import RandomPlayer + from sample_players import GreedyPlayer # create an isolation board (by default 7x7) player1 = RandomPlayer() @@ -27,7 +47,7 @@ The following example creates a game and illustrates the basic API. You can run # place player 1 on the board at row 2, column 3, then place player 2 on # the board at row 0, column 5; display the resulting board state. Note - # that .apply_move() changes the calling object + # that the .apply_move() method changes the calling object in-place. game.apply_move((2, 3)) game.apply_move((0, 5)) print(game.to_string()) @@ -47,59 +67,79 @@ The following example creates a game and illustrates the basic API. You can run print("\nNew state:\n{}".format(new_game.to_string())) # play the remainder of the game automatically -- outcome can be "illegal - # move" or "timeout"; it should _always_ be "illegal move" in this example + # move", "timeout", or "forfeit" winner, history, outcome = game.play() print("\nWinner: {}\nOutcome: {}".format(winner, outcome)) print(game.to_string()) print("Move history:\n{!s}".format(history)) -## Instructions +### Coding -Implement the following four functions in `game_agent.py`: +The steps below outline a suggested process for completing the project -- however, this is just a suggestion to help you get started. -- `CustomPlayer.minimax()`: implement minimax search -- `CustomPlayer.alphabeta()`: implement minimax search with alpha-beta pruning -- `CustomPlayer.get_move()`: implement fixed-depth and iterative deepening search -- `custom_score()`: implement your own position evaluation heuristic +A stub for writing unit tests is provided in the [`test_game_agent.py`](tests/test_game_agent.py) file (no local test cases are provided). In order to run your tests, execute `python -m unittest` command (See the [unittest](https://docs.python.org/3/library/unittest.html#basic-example) module for information on getting started.) -You may write or modify code within each file (as long as you maintain compatibility with the function signatures provided) and you may add other classes, functions, etc., as needed, but it is not required. +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. +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.) -### Coding +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. -The steps below outline one suggested process for completing the project -- however, this is just a suggestion to help you get started. Unit tests can be executed by running `python agent_test.py -v`. (See the [unittest](https://docs.python.org/3/library/unittest.html#basic-example) module for details.) +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. -0. Pass the test_get_move_interface and test_minimax_interface unit tests by implementing a fixed-depth call to minimax in `CustomPlayer.get_move()` and implementing a single-level search in `CustomPlayer.minimax()` (the interface checks only tests depth=1) +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. -0. Pass the test_minimax test by extending your `CustomPlayer.minimax()` function with the full recursive search process. See Also: [AIMA Minimax Decision](https://github.com/aimacode/aima-pseudocode/blob/master/md/Minimax-Decision.md) +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. -0. Pass the test_alphabeta_interface test by copying the code from `CustomPlayer.minimax()` into the `CustomPlayer.alphabeta()` function. +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. -0. Pass the test_alphabeta test by extending your `CustomPlayer.alphabeta()` function to include alpha and beta pruning. See Also: [AIMA Alpha-Beta Search](https://github.com/aimacode/aima-pseudocode/blob/master/md/Alpha-Beta-Search.md) +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) -0. Pass the test_get_move test by extending your fixed-depth call in `CustomPlayer.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) - -0. Finally, pass the test_heuristic test by implementing any heuristic in `custom_score()`. (This test only validates the return value type -- it does not check for "correctness" of your heuristic.) You can see example heuristics in the `sample_players.py` file. +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. ### Tournament -The `tournament.py` script is used to evaluate the effectiveness of your custom_score heuristic. The script measures relative performance of your agent (called "Student") in a round-robin tournament against several other pre-defined agents. The Student agent uses time-limited Iterative Deepening and the custom_score heuristic you wrote. +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. -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 uess Iterative Deepening and the improved_score heuristic from `sample_players.py`. Your goal is to develop a heuristic such that Student outperforms ID_Improved. +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!) The tournament opponents are listed below. (See also: sample heuristics and players defined in sample_players.py) - Random: An agent that randomly chooses a move each turn. -- MM_Null: CustomPlayer agent using fixed-depth minimax search and the null_score heuristic -- MM_Open: CustomPlayer agent using fixed-depth minimax search and the open_move_score heuristic -- MM_Improved: CustomPlayer agent using fixed-depth minimax search and the improved_score heuristic -- AB_Null: CustomPlayer agent using fixed-depth alpha-beta search and the null_score heuristic -- AB_Open: CustomPlayer agent using fixed-depth alpha-beta search and the open_move_score heuristic -- AB_Improved: CustomPlayer agent using fixed-depth alpha-beta search and the improved_score heuristic +- MM_Open: MinimaxPlayer agent using the open_move_score heuristic with search depth 3 +- MM_Center: MinimaxPlayer agent using the center_score heuristic with search depth 3 +- MM_Improved: MinimaxPlayer agent using the improved_score heuristic with search depth 3 +- AB_Open: AlphaBetaPlayer using iterative deepening alpha-beta search and the open_move_score heuristic +- AB_Center: AlphaBetaPlayer using iterative deepening alpha-beta search and the center_score heuristic +- AB_Improved: AlphaBetaPlayer using iterative deepening alpha-beta search and the improved_score heuristic + +## Submission + +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. + +Please see the instructions in the [AIND-Sudoku](https://github.com/udacity/AIND-Sudoku#submission) project repository for installation and setup instructions. + +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). + +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. + + +## Game Visualization + +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.) + + +## PvP Competition + +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. +The competition agent can be submitted using the Udacity project assistant: -## Submitting + udacity submit isolation-pvp -Your project is ready for submission when it meets all requirements of the project rubric. Your code is finished when it passes all unit tests, and you have successfully implemented a suitable heuristic function. + # Archival Note + This repository is deprecated; therefore, we are going to archive it. However, learners will be able to fork it to their personal Github account but cannot submit PRs to this repository. If you have any issues or suggestions to make, feel free to: +- Utilize the https://knowledge.udacity.com/ forum to seek help on content-specific issues. +- Submit a support ticket along with the link to your forked repository if (learners are) blocked for other reasons. Here are the links for the [retail consumers](https://udacity.zendesk.com/hc/en-us/requests/new) and [enterprise learners](https://udacityenterprise.zendesk.com/hc/en-us/requests/new?ticket_form_id=360000279131). \ No newline at end of file diff --git a/agent_test.py b/agent_test.py deleted file mode 100644 index e0bf43f9..00000000 --- a/agent_test.py +++ /dev/null @@ -1,540 +0,0 @@ -""" -This file contains test cases to verify the correct implementation of the -functions required for this project including minimax, alphabeta, and iterative -deepening. The heuristic function is tested for conformance to the expected -interface, but cannot be automatically assessed for correctness. - -STUDENTS SHOULD NOT NEED TO MODIFY THIS CODE. IT WOULD BE BEST TO TREAT THIS -FILE AS A BLACK BOX FOR TESTING. -""" -import random -import unittest -import timeit -import sys - -import isolation -import game_agent - -from collections import Counter -from copy import deepcopy -from copy import copy -from functools import wraps -from queue import Queue -from threading import Thread -from multiprocessing import TimeoutError -from queue import Empty as QueueEmptyError -from importlib import reload - -WRONG_MOVE = """ -The {} function failed because it returned a non-optimal move at search depth {}. -Valid choices: {} -Your selection: {} -""" - -WRONG_NUM_EXPLORED = """ -Your {} search visited the wrong nodes at search depth {}. If the number -of visits is too large, make sure that iterative deepening is only -running when the `iterative` flag is set in the agent constructor. -Max explored size: {} -Number you explored: {} -""" - -UNEXPECTED_VISIT = """ -Your {} search did not visit the number of expected unique nodes at search -depth {}. -Max explored size: {} -Number you explored: {} -""" - -ID_FAIL = """ -Your agent explored the wrong number of nodes using Iterative Deepening and -minimax. Remember that ID + MM should check every node in each layer of the -game tree before moving on to the next layer. -""" - -INVALID_MOVE = """ -Your agent returned an invalid move. Make sure that your function returns -a selection when the search times out during iterative deepening. -Valid choices: {!s} -Your choice: {} -""" - -TIMER_MARGIN = 15 # time (in ms) to leave on the timer to avoid timeout - - -def curr_time_millis(): - """Simple timer to return the current clock time in milliseconds.""" - return 1000 * timeit.default_timer() - - -def handler(obj, testcase, queue): - """Handler to pass information between threads; used in the timeout - function to abort long-running (i.e., probably hung) test cases. - """ - try: - queue.put((None, testcase(obj))) - except: - queue.put((sys.exc_info(), None)) - - -def timeout(time_limit): - """Function decorator for unittest test cases to specify test case timeout. - - The timer mechanism works by spawning a new thread for the test to run in - and using the timeout handler for the thread-safe queue class to abort and - kill the child thread if it doesn't return within the timeout. - - It is not safe to access system resources (e.g., files) within test cases - wrapped by this timer. - """ - - def wrapUnitTest(testcase): - - @wraps(testcase) - def testWrapper(self): - - queue = Queue() - - try: - p = Thread(target=handler, args=(self, testcase, queue)) - p.daemon = True - p.start() - err, res = queue.get(timeout=time_limit) - p.join() - if err: - raise err[0](err[1]).with_traceback(err[2]) - return res - except QueueEmptyError: - raise TimeoutError("Test aborted due to timeout. Test was " + - "expected to finish in less than {} second(s).".format(time_limit)) - - return testWrapper - - return wrapUnitTest - - -def makeEvalTable(table): - """Use a closure to create a heuristic function that returns values from - a table that maps board locations to constant values. This supports testing - the minimax and alphabeta search functions. - - THIS HEURISTIC IS ONLY USEFUL FOR TESTING THE SEARCH FUNCTIONALITY - - IT IS NOT MEANT AS AN EXAMPLE OF A USEFUL HEURISTIC FOR GAME PLAYING. - """ - - def score(game, player): - row, col = game.get_player_location(player) - return table[row][col] - - return score - - -def makeEvalStop(limit, timer, value=None): - """Use a closure to create a heuristic function that forces the search - timer to expire when a fixed number of node expansions have been perfomred - during the search. This ensures that the search algorithm should always be - in a predictable state regardless of node expansion order. - - THIS HEURISTIC IS ONLY USEFUL FOR TESTING THE SEARCH FUNCTIONALITY - - IT IS NOT MEANT AS AN EXAMPLE OF A USEFUL HEURISTIC FOR GAME PLAYING. - """ - - def score(game, player): - if timer.time_left() < 0: - raise TimeoutError("Timer expired during search. You must " + - "return an answer before the timer reaches 0.") - if limit == game.counts[0]: - timer.time_limit = 0 - return 0 - - return score - - -def makeBranchEval(first_branch): - """Use a closure to create a heuristic function that evaluates to a nonzero - score when the root of the search is the first branch explored, and - otherwise returns 0. This heuristic is used to force alpha-beta to prune - some parts of a game tree for testing. - - THIS HEURISTIC IS ONLY USEFUL FOR TESTING THE SEARCH FUNCTIONALITY - - IT IS NOT MEANT AS AN EXAMPLE OF A USEFUL HEURISTIC FOR GAME PLAYING. - """ - - def score(game, player): - if not first_branch: - first_branch.append(game.root) - if game.root in first_branch: - return 1. - return 0. - - return score - - -class CounterBoard(isolation.Board): - """Subclass of the isolation board that maintains counters for the number - of unique nodes and total nodes visited during depth first search. - - Some functions from the base class must be overridden to maintain the - counters during search. - """ - - def __init__(self, *args, **kwargs): - super(CounterBoard, self).__init__(*args, **kwargs) - self.counter = Counter() - self.visited = set() - self.root = None - - def copy(self): - new_board = CounterBoard(self.__player_1__, self.__player_2__, - width=self.width, height=self.height) - new_board.move_count = self.move_count - new_board.__active_player__ = self.__active_player__ - new_board.__inactive_player__ = self.__inactive_player__ - new_board.__last_player_move__ = copy(self.__last_player_move__) - new_board.__player_symbols__ = copy(self.__player_symbols__) - new_board.__board_state__ = deepcopy(self.__board_state__) - new_board.counter = self.counter - new_board.visited = self.visited - new_board.root = self.root - return new_board - - def forecast_move(self, move): - self.counter[move] += 1 - self.visited.add(move) - new_board = self.copy() - new_board.apply_move(move) - if new_board.root is None: - new_board.root = move - return new_board - - @property - def counts(self): - """ Return counts of (total, unique) nodes visited """ - return sum(self.counter.values()), len(self.visited) - - -class Project1Test(unittest.TestCase): - - def initAUT(self, depth, eval_fn, iterative=False, - method="minimax", loc1=(3, 3), loc2=(0, 0), w=7, h=7): - """Generate and initialize player and board objects to be used for - testing. - """ - reload(game_agent) - agentUT = game_agent.CustomPlayer(depth, eval_fn, iterative, method) - board = CounterBoard(agentUT, 'null_agent', w, h) - board.apply_move(loc1) - board.apply_move(loc2) - return agentUT, board - - @timeout(5) - # @unittest.skip("Skip eval function test.") # Uncomment this line to skip test - def test_heuristic(self): - """ Test output interface of heuristic score function interface.""" - - player1 = "Player1" - player2 = "Player2" - p1_location = (0, 0) - p2_location = (1, 1) # top left corner - game = isolation.Board(player1, player2) - game.apply_move(p1_location) - game.apply_move(p2_location) - - self.assertIsInstance(game_agent.custom_score(game, player1), float, - "The heuristic function should return a floating point") - - timeout(5) - # @unittest.skip("Skip simple minimax test.") # Uncomment this line to skip test - def test_minimax_interface(self): - """ Test CustomPlayer.minimax interface with simple input """ - h, w = 7, 7 # board size - test_depth = 1 - starting_location = (5, 3) - adversary_location = (0, 0) # top left corner - iterative_search = False - search_method = "minimax" - heuristic = lambda g, p: 0. # return 0 everywhere - - # create a player agent & a game board - agentUT = game_agent.CustomPlayer( - test_depth, heuristic, iterative_search, search_method) - agentUT.time_left = lambda: 99 # ignore timeout for fixed-depth search - board = isolation.Board(agentUT, 'null_agent', w, h) - - # place two "players" on the board at arbitrary (but fixed) locations - board.apply_move(starting_location) - board.apply_move(adversary_location) - - for move in board.get_legal_moves(): - next_state = board.forecast_move(move) - v, _ = agentUT.minimax(next_state, test_depth) - - self.assertTrue(type(v) == float, - ("Minimax function should return a floating " + - "point value approximating the score for the " + - "branch being searched.")) - - timeout(5) - # @unittest.skip("Skip alphabeta test.") # Uncomment this line to skip test - def test_alphabeta_interface(self): - """ Test CustomPlayer.alphabeta interface with simple input """ - h, w = 9, 9 # board size - test_depth = 1 - starting_location = (2, 7) - adversary_location = (0, 0) # top left corner - iterative_search = False - search_method = "alphabeta" - heuristic = lambda g, p: 0. # return 0 everywhere - - # create a player agent & a game board - agentUT = game_agent.CustomPlayer( - test_depth, heuristic, iterative_search, search_method) - agentUT.time_left = lambda: 99 # ignore timeout for fixed-depth search - board = isolation.Board(agentUT, 'null_agent', w, h) - - # place two "players" on the board at arbitrary (but fixed) locations - board.apply_move(starting_location) - board.apply_move(adversary_location) - - for move in board.get_legal_moves(): - next_state = board.forecast_move(move) - v, _ = agentUT.alphabeta(next_state, test_depth) - - self.assertTrue(type(v) == float, - ("Alpha Beta function should return a floating " + - "point value approximating the score for the " + - "branch being searched.")) - - @timeout(5) - # @unittest.skip("Skip get_move test.") # Uncomment this line to skip test - def test_get_move_interface(self): - """ Test CustomPlayer.get_move interface with simple input """ - h, w = 9, 9 # board size - test_depth = 1 - starting_location = (2, 7) - adversary_location = (0, 0) # top left corner - iterative_search = False - search_method = "minimax" - heuristic = lambda g, p: 0. # return 0 everywhere - - # create a player agent & a game board - agentUT = game_agent.CustomPlayer( - test_depth, heuristic, iterative_search, search_method) - - # Test that get_move returns a legal choice on an empty game board - board = isolation.Board(agentUT, 'null_agent', w, h) - legal_moves = board.get_legal_moves() - move = agentUT.get_move(board, legal_moves, lambda: 99) - self.assertIn(move, legal_moves, - ("The get_move() function failed as player 1 on an " + - "empty board. It should return coordinates on the " + - "game board for the location of the agent's next " + - "move. The move must be one of the legal moves on " + - "the current game board.")) - - # Test that get_move returns a legal choice for first move as player 2 - board = isolation.Board('null_agent', agentUT, w, h) - board.apply_move(starting_location) - legal_moves = board.get_legal_moves() - move = agentUT.get_move(board, legal_moves, lambda: 99) - self.assertIn(move, legal_moves, - ("The get_move() function failed making the first " + - "move as player 2 on a new board. It should return " + - "coordinates on the game board for the location " + - "of the agent's next move. The move must be one " + - "of the legal moves on the current game board.")) - - # Test that get_move returns a legal choice after first move - board = isolation.Board(agentUT, 'null_agent', w, h) - board.apply_move(starting_location) - board.apply_move(adversary_location) - legal_moves = board.get_legal_moves() - move = agentUT.get_move(board, legal_moves, lambda: 99) - self.assertIn(move, legal_moves, - ("The get_move() function failed as player 1 on a " + - "game in progress. It should return coordinates on" + - "the game board for the location of the agent's " + - "next move. The move must be one of the legal moves " + - "on the current game board.")) - - @timeout(5) - # @unittest.skip("Skip minimax test.") # Uncomment this line to skip test - def test_minimax(self): - """ Test CustomPlayer.minimax - - This test uses a scoring function that returns a constant value based - on the location of the search agent on the board to force minimax to - choose a branch that visits those cells at a specific fixed-depth. - If minimax is working properly, it will visit a constant number of - nodes during the search and return one of the acceptable legal moves. - """ - h, w = 7, 7 # board size - starting_location = (2, 3) - adversary_location = (0, 0) # top left corner - iterative_search = False - method = "minimax" - - # The agent under test starts at position (2, 3) on the board, which - # gives eight (8) possible legal moves [(0, 2), (0, 4), (1, 1), (1, 5), - # (3, 1), (3, 5), (4, 2), (4, 4)]. The search function will pick one of - # those moves based on the estimated score for each branch. The value - # only changes on odd depths because even depths end on when the - # adversary has initiative. - value_table = [[0] * w for _ in range(h)] - value_table[1][5] = 1 # depth 1 & 2 - value_table[4][3] = 2 # depth 3 & 4 - value_table[6][6] = 3 # depth 5 - heuristic = makeEvalTable(value_table) - - # These moves are the branches that will lead to the cells in the value - # table for the search depths. - expected_moves = [set([(1, 5)]), - set([(3, 1), (3, 5)]), - set([(3, 5), (4, 2)])] - - # Expected number of node expansions during search - counts = [(8, 8), (24, 10), (92, 27), (418, 32), (1650, 43)] - - # Test fixed-depth search; note that odd depths mean that the searching - # player (student agent) has the last move, while even depths mean that - # the adversary has the last move before calling the heuristic - # evaluation function. - for idx in range(5): - test_depth = idx + 1 - agentUT, board = self.initAUT(test_depth, heuristic, - iterative_search, method, - loc1=starting_location, - loc2=adversary_location) - - # disable search timeout by returning a constant value - agentUT.time_left = lambda: 1e3 - _, move = agentUT.minimax(board, test_depth) - - num_explored_valid = board.counts[0] == counts[idx][0] - num_unique_valid = board.counts[1] == counts[idx][1] - - self.assertTrue(num_explored_valid, WRONG_NUM_EXPLORED.format( - method, test_depth, counts[idx][0], board.counts[0])) - - self.assertTrue(num_unique_valid, UNEXPECTED_VISIT.format( - method, test_depth, counts[idx][1], board.counts[1])) - - self.assertIn(move, expected_moves[idx // 2], WRONG_MOVE.format( - method, test_depth, expected_moves[idx // 2], move)) - - @timeout(20) - # @unittest.skip("Skip alpha-beta test.") # Uncomment this line to skip test - def test_alphabeta(self): - """ Test CustomPlayer.alphabeta - - This test uses a scoring function that returns a constant value based - on the branch being searched by alphabeta in the user agent, and forces - the search to prune on every other branch it visits. By using a huge - board where the players are too far apart to interact and every branch - has the same growth factor, the expansion and pruning must result in - an exact number of expanded nodes. - """ - h, w = 101, 101 # board size - starting_location = (50, 50) - adversary_location = (0, 0) # top left corner - iterative_search = False - method = "alphabeta" - - # The agent under test starts in the middle of a huge board so that - # every branch has the same number of possible moves, so pruning any - # branch has the same effect during testing - - # These are the expected number of node expansions for alphabeta search - # to explore the game tree to fixed depth. The custom eval function - # used for this test ensures that some branches must be pruned, while - # the search should still return an optimal move. - counts = [(8, 8), (17, 10), (74, 42), (139, 51), (540, 119)] - - for idx in range(len(counts)): - test_depth = idx + 1 # pruning guarantee requires min depth of 3 - first_branch = [] - heuristic = makeBranchEval(first_branch) - agentUT, board = self.initAUT(test_depth, heuristic, - iterative_search, method, - loc1=starting_location, - loc2=adversary_location, - w=w, h=h) - - # disable search timeout by returning a constant value - agentUT.time_left = lambda: 1e3 - _, move = agentUT.alphabeta(board, test_depth) - - num_explored_valid = board.counts[0] == counts[idx][0] - num_unique_valid = board.counts[1] == counts[idx][1] - - self.assertTrue(num_explored_valid, WRONG_NUM_EXPLORED.format( - method, test_depth, counts[idx][0], board.counts[0])) - - self.assertTrue(num_unique_valid, UNEXPECTED_VISIT.format( - method, test_depth, counts[idx][1], board.counts[1])) - - self.assertIn(move, first_branch, WRONG_MOVE.format( - method, test_depth, first_branch, move)) - - - @timeout(20) - # @unittest.skip("Skip iterative deepening test.") # Uncomment this line to skip test - def test_get_move(self): - """ Test iterative deepening in CustomPlayer.get_move by placing an - agent on the game board and performing ID minimax search, which - should visit a specific number of unique nodes while expanding. By - forcing the search to timeout when a predetermined number of nodes - have been expanded, we can then verify that the expected number of - unique nodes have been visited. - """ - - class DynamicTimer(): - """Dynamic Timer allows the time limit to be changed after the - timer is initialized so that the search timeout can be triggered - before the timer actually expires. This allows the timer to expire - when an event occurs, regardless of the clock time required until - the event happens. - """ - def __init__(self, time_limit): - self.time_limit = time_limit - self.start_time = curr_time_millis() - - def time_left(self): - return self.time_limit - (curr_time_millis() - self.start_time) - - w, h = 11, 11 # board size - adversary_location = (0, 0) - method = "minimax" - - # The agent under test starts at the positions indicated below, and - # performs an iterative deepening minimax search (minimax is easier to - # test because it always visits all nodes in the game tree at every - # level). - origins = [(2, 3), (6, 6), (7, 4), (4, 2), (0, 5), (10, 10)] - exact_counts = [(8, 8), (32, 10), (160, 39), (603, 35), (1861, 54), (3912, 62)] - - for idx in range(len(origins)): - - # set the initial timer high enough that the search will not - # timeout before triggering the dynamic timer to halt by visiting - # the expected number of nodes - time_limit = 1e4 - timer = DynamicTimer(time_limit) - eval_fn = makeEvalStop(exact_counts[idx][0], timer, time_limit) - agentUT, board = self.initAUT(-1, eval_fn, True, method, - origins[idx], adversary_location, - w, h) - legal_moves = board.get_legal_moves() - chosen_move = agentUT.get_move(board, legal_moves, timer.time_left) - - diff_total = abs(board.counts[0] - exact_counts[idx][0]) - diff_unique = abs(board.counts[1] - exact_counts[idx][1]) - - self.assertTrue(diff_total <= 1 and diff_unique == 0, ID_FAIL) - - self.assertTrue(chosen_move in legal_moves, INVALID_MOVE.format( - legal_moves, chosen_move)) - - -if __name__ == '__main__': - unittest.main() diff --git a/competition_agent.py b/competition_agent.py new file mode 100644 index 00000000..c0df450f --- /dev/null +++ b/competition_agent.py @@ -0,0 +1,97 @@ +"""Implement your own custom search agent using any combination of techniques +you choose. This agent will compete against other students (and past +champions) in a tournament. + + COMPLETING AND SUBMITTING A COMPETITION AGENT IS OPTIONAL +""" +import random + + +class SearchTimeout(Exception): + """Subclass base exception for code clarity. """ + pass + + +def custom_score(game, player): + """Calculate the heuristic value of a game state from the point of view + of the given player. + + This should be the best heuristic function for your project submission. + + Parameters + ---------- + game : `isolation.Board` + An instance of `isolation.Board` encoding the current state of the + game (e.g., player locations and blocked cells). + + player : object + A player instance in the current game (i.e., an object corresponding to + one of the player objects `game.__player_1__` or `game.__player_2__`.) + + Returns + ------- + float + The heuristic value of the current game state to the specified player. + """ + raise NotImplementedError + + +class CustomPlayer: + """Game-playing agent to use in the optional player vs player Isolation + competition. + + You must at least implement the get_move() method and a search function + to complete this class, but you may use any of the techniques discussed + in lecture or elsewhere on the web -- opening books, MCTS, etc. + + ************************************************************************** + THIS CLASS IS OPTIONAL -- IT IS ONLY USED IN THE ISOLATION PvP + COMPETITION. IT IS NOT REQUIRED FOR THE ISOLATION PROJECT REVIEW. + ************************************************************************** + + Parameters + ---------- + data : string + The name of the search method to use in get_move(). + + timeout : float (optional) + Time remaining (in milliseconds) when search is aborted. Note that + the PvP competition uses more accurate timers that are not cross- + platform compatible, so a limit of 1ms (vs 10ms for the other classes) + is generally sufficient. + """ + + def __init__(self, data=None, timeout=1.): + self.score = custom_score + self.time_left = None + self.TIMER_THRESHOLD = timeout + + def get_move(self, game, time_left): + """Search for the best move from the available legal moves and return a + result before the time limit expires. + + ********************************************************************** + NOTE: If time_left < 0 when this function returns, the agent will + forfeit the game due to timeout. You must return _before_ the + timer reaches 0. + ********************************************************************** + + Parameters + ---------- + game : `isolation.Board` + An instance of `isolation.Board` encoding the current state of the + game (e.g., player locations and blocked cells). + + time_left : callable + A function that returns the number of milliseconds left in the + current turn. Returning with any less than 0 ms remaining forfeits + the game. + + Returns + ------- + (int, int) + Board coordinates corresponding to a legal move; may return + (-1, -1) if there are no available legal moves. + """ + # OPTIONAL: Finish this function! + raise NotImplementedError diff --git a/game_agent.py b/game_agent.py index 4f426a84..f6efc037 100644 --- a/game_agent.py +++ b/game_agent.py @@ -1,16 +1,12 @@ -"""This file contains all the classes you must complete for this project. - -You can use the test cases in agent_test.py to help during development, and -augment the test suite with your own test cases to further test your code. - -You must test your agent's strength against a set of agents with known -relative strength using tournament.py and include the results in your report. +"""Finish all TODO items in this file to complete the isolation project, then +test your agent's strength against a set of known agents using tournament.py +and include the results in your report. """ import random -class Timeout(Exception): - """Subclass base exception for code clarity.""" +class SearchTimeout(Exception): + """Subclass base exception for code clarity. """ pass @@ -18,6 +14,8 @@ def custom_score(game, player): """Calculate the heuristic value of a game state from the point of view of the given player. + This should be the best heuristic function for your project submission. + Note: this function should be called from within a Player instance as `self.score()` -- you should not need to call this function directly. @@ -36,16 +34,67 @@ def custom_score(game, player): float The heuristic value of the current game state to the specified player. """ + # TODO: finish this function! + raise NotImplementedError + + +def custom_score_2(game, player): + """Calculate the heuristic value of a game state from the point of view + of the given player. + + Note: this function should be called from within a Player instance as + `self.score()` -- you should not need to call this function directly. + + Parameters + ---------- + game : `isolation.Board` + An instance of `isolation.Board` encoding the current state of the + game (e.g., player locations and blocked cells). + + player : object + A player instance in the current game (i.e., an object corresponding to + one of the player objects `game.__player_1__` or `game.__player_2__`.) + Returns + ------- + float + The heuristic value of the current game state to the specified player. + """ # TODO: finish this function! raise NotImplementedError -class CustomPlayer: - """Game-playing agent that chooses a move using your evaluation function - and a depth-limited minimax algorithm with alpha-beta pruning. You must - finish and test this player to make sure it properly uses minimax and - alpha-beta to return a good move before the search time limit expires. +def custom_score_3(game, player): + """Calculate the heuristic value of a game state from the point of view + of the given player. + + Note: this function should be called from within a Player instance as + `self.score()` -- you should not need to call this function directly. + + Parameters + ---------- + game : `isolation.Board` + An instance of `isolation.Board` encoding the current state of the + game (e.g., player locations and blocked cells). + + player : object + A player instance in the current game (i.e., an object corresponding to + one of the player objects `game.__player_1__` or `game.__player_2__`.) + + Returns + ------- + float + The heuristic value of the current game state to the specified player. + """ + # TODO: finish this function! + raise NotImplementedError + + +class IsolationPlayer: + """Base class for minimax and alphabeta agents -- this class is never + constructed or tested directly. + + ******************** DO NOT MODIFY THIS CLASS ******************** Parameters ---------- @@ -58,41 +107,34 @@ class CustomPlayer: score_fn : callable (optional) A function to use for heuristic evaluation of game states. - iterative : boolean (optional) - Flag indicating whether to perform fixed-depth search (False) or - iterative deepening search (True). - - method : {'minimax', 'alphabeta'} (optional) - The name of the search method to use in get_move(). - timeout : float (optional) Time remaining (in milliseconds) when search is aborted. Should be a positive value large enough to allow the function to return before the timer expires. """ - - def __init__(self, search_depth=3, score_fn=custom_score, - iterative=True, method='minimax', timeout=10.): + def __init__(self, search_depth=3, score_fn=custom_score, timeout=10.): self.search_depth = search_depth - self.iterative = iterative self.score = score_fn - self.method = method self.time_left = None self.TIMER_THRESHOLD = timeout - def get_move(self, game, legal_moves, time_left): + +class MinimaxPlayer(IsolationPlayer): + """Game-playing agent that chooses a move using depth-limited minimax + search. You must finish and test this player to make sure it properly uses + minimax to return a good move before the search time limit expires. + """ + + def get_move(self, game, time_left): """Search for the best move from the available legal moves and return a result before the time limit expires. - This function must perform iterative deepening if self.iterative=True, - and it must use the search method (minimax or alphabeta) corresponding - to the self.method value. + ************** YOU DO NOT NEED TO MODIFY THIS FUNCTION ************* - ********************************************************************** - NOTE: If time_left < 0 when this function returns, the agent will - forfeit the game due to timeout. You must return _before_ the - timer reaches 0. - ********************************************************************** + For fixed-depth search, this function simply wraps the call to the + minimax method, but this method provides a common interface for all + Isolation agents, and you will replace it in the AlphaBetaPlayer with + iterative deepening search. Parameters ---------- @@ -100,10 +142,6 @@ def get_move(self, game, legal_moves, time_left): An instance of `isolation.Board` encoding the current state of the game (e.g., player locations and blocked cells). - legal_moves : list<(int, int)> - A list containing legal moves. Moves are encoded as tuples of pairs - of ints defining the next (row, col) for the agent to occupy. - time_left : callable A function that returns the number of milliseconds left in the current turn. Returning with any less than 0 ms remaining forfeits @@ -115,31 +153,34 @@ def get_move(self, game, legal_moves, time_left): Board coordinates corresponding to a legal move; may return (-1, -1) if there are no available legal moves. """ - self.time_left = time_left - # TODO: finish this function! - - # Perform any required initializations, including selecting an initial - # move from the game board (i.e., an opening book), or returning - # immediately if there are no legal moves + # Initialize the best move so that this function returns something + # in case the search fails due to timeout + best_move = (-1, -1) try: - # The search method call (alpha beta or minimax) should happen in - # here in order to avoid timeout. The try/except block will - # automatically catch the exception raised by the search method - # when the timer gets close to expiring - pass + # The try/except block will automatically catch the exception + # raised when the timer is about to expire. + return self.minimax(game, self.search_depth) - except Timeout: - # Handle any actions required at timeout, if necessary - pass + except SearchTimeout: + pass # Handle any actions required after timeout as needed # Return the best move from the last completed search iteration - raise NotImplementedError + return best_move + + def minimax(self, game, depth): + """Implement depth-limited minimax search algorithm as described in + the lectures. - def minimax(self, game, depth, maximizing_player=True): - """Implement the minimax search algorithm as described in the lectures. + This should be a modified version of MINIMAX-DECISION in the AIMA text. + https://github.com/aimacode/aima-pseudocode/blob/master/md/Minimax-Decision.md + + ********************************************************************** + You MAY add additional methods to this class, or define helper + functions to implement the required functionality. + ********************************************************************** Parameters ---------- @@ -151,33 +192,82 @@ def minimax(self, game, depth, maximizing_player=True): Depth is an integer representing the maximum number of plies to search in the game tree before aborting - maximizing_player : bool - Flag indicating whether the current search depth corresponds to a - maximizing layer (True) or a minimizing layer (False) - Returns ------- - float - The score for the current search branch - - tuple(int, int) - The best move for the current branch; (-1, -1) for no legal moves + (int, int) + The board coordinates of the best move found in the current search; + (-1, -1) if there are no legal moves Notes ----- (1) You MUST use the `self.score()` method for board evaluation - to pass the project unit tests; you cannot call any other - evaluation function directly. + to pass the project tests; you cannot call any other evaluation + function directly. + + (2) If you use any helper functions (e.g., as shown in the AIMA + pseudocode) then you must copy the timer check into the top of + each helper function or else your agent will timeout during + testing. """ if self.time_left() < self.TIMER_THRESHOLD: - raise Timeout() + raise SearchTimeout() # TODO: finish this function! raise NotImplementedError - def alphabeta(self, game, depth, alpha=float("-inf"), beta=float("inf"), maximizing_player=True): - """Implement minimax search with alpha-beta pruning as described in the - lectures. + +class AlphaBetaPlayer(IsolationPlayer): + """Game-playing agent that chooses a move using iterative deepening minimax + search with alpha-beta pruning. You must finish and test this player to + make sure it returns a good move before the search time limit expires. + """ + + def get_move(self, game, time_left): + """Search for the best move from the available legal moves and return a + result before the time limit expires. + + Modify the get_move() method from the MinimaxPlayer class to implement + iterative deepening search instead of fixed-depth search. + + ********************************************************************** + NOTE: If time_left() < 0 when this function returns, the agent will + forfeit the game due to timeout. You must return _before_ the + timer reaches 0. + ********************************************************************** + + Parameters + ---------- + game : `isolation.Board` + An instance of `isolation.Board` encoding the current state of the + game (e.g., player locations and blocked cells). + + time_left : callable + A function that returns the number of milliseconds left in the + current turn. Returning with any less than 0 ms remaining forfeits + the game. + + Returns + ------- + (int, int) + Board coordinates corresponding to a legal move; may return + (-1, -1) if there are no available legal moves. + """ + self.time_left = time_left + + # TODO: finish this function! + raise NotImplementedError + + def alphabeta(self, game, depth, alpha=float("-inf"), beta=float("inf")): + """Implement depth-limited minimax search with alpha-beta pruning as + described in the lectures. + + This should be a modified version of ALPHA-BETA-SEARCH in the AIMA text + https://github.com/aimacode/aima-pseudocode/blob/master/md/Alpha-Beta-Search.md + + ********************************************************************** + You MAY add additional methods to this class, or define helper + functions to implement the required functionality. + ********************************************************************** Parameters ---------- @@ -195,26 +285,25 @@ def alphabeta(self, game, depth, alpha=float("-inf"), beta=float("inf"), maximiz beta : float Beta limits the upper bound of search on maximizing layers - maximizing_player : bool - Flag indicating whether the current search depth corresponds to a - maximizing layer (True) or a minimizing layer (False) - Returns ------- - float - The score for the current search branch - - tuple(int, int) - The best move for the current branch; (-1, -1) for no legal moves + (int, int) + The board coordinates of the best move found in the current search; + (-1, -1) if there are no legal moves Notes ----- (1) You MUST use the `self.score()` method for board evaluation - to pass the project unit tests; you cannot call any other - evaluation function directly. + to pass the project tests; you cannot call any other evaluation + function directly. + + (2) If you use any helper functions (e.g., as shown in the AIMA + pseudocode) then you must copy the timer check into the top of + each helper function or else your agent will timeout during + testing. """ if self.time_left() < self.TIMER_THRESHOLD: - raise Timeout() + raise SearchTimeout() # TODO: finish this function! raise NotImplementedError diff --git a/isolation/README.md b/isolation/README.md new file mode 100644 index 00000000..18854f92 --- /dev/null +++ b/isolation/README.md @@ -0,0 +1,86 @@ + +# isolation.Board class + +## Constructor + + Board.__init__(self, player_1, player_2, width=7, height=7) + +## Attributes + +### BLANK : 0 (constant) + +### NOT_MOVED : None (constant) + +### width : 7 (constant) + +Board width + +### height : 7 (constant) + +Board height + +### active_player : hashable + +Reference to a hashable object registered as a player with the initiative to move on the current board + +### inactive_player : hashable + +Reference to a hashable object registered as a player awaiting initiative to move on the current board + +### move_count : int + +Counter indicating the number of moves that have been applied to the game + +## Public Methods + +### apply_move(self, move) + +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. + +### copy(self) + +Return a new Board object that is a copy of the current game state + +### forecast_move(self, move) + +Equivalent to apply_move, but returns a copy of the board rather than modifying the state in-place. + +### get_blank_spaces(self) + +Returns a list of tuples identifying the blank squares on the current board + +### get_legal_moves(self, player=None) + +Returns a list of tuples identifying the legal moves for the specified player + +### get_opponent(self, player) + +Returns the opponent of the specified player + +### get_player_location(self, player) + +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. + +### hash(self) + +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: + +### is_loser(self, player) + +Returns True if the specified player has lost the game in the current state, and False otherwise + +### is_winner(self, player) + +Returns True if the specified player has won the game in the current state, and False otherwise + +### move_is_legal(self, move) + +Returns True if the active player can legally make the specified move and False otherwise + +### to_string(self, symbols=['1', '2']) + +Return a string representation of the current board position + +### utility(self, player) + +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. \ No newline at end of file diff --git a/isolation/__init__.py b/isolation/__init__.py index 55f44f52..ae53922d 100644 --- a/isolation/__init__.py +++ b/isolation/__init__.py @@ -7,62 +7,5 @@ legal moves loses, and the opponent is declared the winner. """ -import io - # Make the Board class available at the root of the module for imports from .isolation import Board - - -def game_as_text(winner, move_history, termination="", board=Board(1, 2)): - """ - Generate a printable representation for a game of isolation. - - Parameters - ---------- - winner : hashable - One of the objects registered by the board object as a valid player. - (i.e., `player` should be either board.__player_1__ or - board.__player_2__). - - move_history : list<[(int, int), (int, int)]> - A list containing an element for each turn in the game encoding the - move applied by each player during their initiative on that turn. - E.g., [(3,3), (1,1)] means that player_1 moved to position (3,3) and - player_2 responded by moving to position (1,1) - - termination : str - String indicating the reason (if any) that the game was terminated. - Valid reasons for termination include "" (none), "timeout", and - "illegal move". - - board : isolation.Board - An instance of `isolation.Board` encoding the game state (e.g., player - locations and blocked cells) for a game of isolation. - - Returns - ---------- - str - A string representation of a game of isolation. - """ - - ans = io.StringIO() - - for i, move in enumerate(move_history): - p1_move = move[0] - ans.write("%d." % i + " (%d,%d)\r\n" % p1_move) - if p1_move != Board.NOT_MOVED: - board.apply_move(p1_move) - ans.write(board.print_board()) - - if len(move) > 1: - p2_move = move[1] - ans.write("%d. ..." % i + " (%d, %d)\r\n" % p2_move) - if p2_move != Board.NOT_MOVED: - board.apply_move(p2_move) - ans.write(board.print_board()) - - ans.write(termination + "\r\n") - - ans.write("Winner: " + str(winner) + "\r\n") - - return ans.getvalue() diff --git a/isolation/isolation.py b/isolation/isolation.py index 4dacdc4c..7316f612 100644 --- a/isolation/isolation.py +++ b/isolation/isolation.py @@ -7,19 +7,15 @@ remain compatible with the defaults provided, and none of your changes will be available to project reviewers. """ - +import random import timeit - -from copy import deepcopy from copy import copy - -TIME_LIMIT_MILLIS = 200 +TIME_LIMIT_MILLIS = 150 class Board(object): - """ - Implement a model for the game Isolation assuming each player moves like + """Implement a model for the game Isolation assuming each player moves like a knight in chess. Parameters @@ -45,33 +41,36 @@ def __init__(self, player_1, player_2, width=7, height=7): self.width = width self.height = height self.move_count = 0 - self.__player_1__ = player_1 - self.__player_2__ = player_2 - self.__active_player__ = player_1 - self.__inactive_player__ = player_2 - self.__board_state__ = [[Board.BLANK for i in range(width)] for j in range(height)] - self.__last_player_move__ = {player_1: Board.NOT_MOVED, player_2: Board.NOT_MOVED} - self.__player_symbols__ = {Board.BLANK: Board.BLANK, player_1: 1, player_2: 2} + self._player_1 = player_1 + self._player_2 = player_2 + self._active_player = player_1 + self._inactive_player = player_2 + + # The last 3 entries of the board state includes initiative (0 for + # player 1, 1 for player 2) player 2 last move, and player 1 last move + self._board_state = [Board.BLANK] * (width * height + 3) + self._board_state[-1] = Board.NOT_MOVED + self._board_state[-2] = Board.NOT_MOVED + + def hash(self): + return str(self._board_state).__hash__() @property def active_player(self): - """ - The object registered as the player holding initiative in the + """The object registered as the player holding initiative in the current game state. """ - return self.__active_player__ + return self._active_player @property def inactive_player(self): - """ - The object registered as the player in waiting for the current + """The object registered as the player in waiting for the current game state. """ - return self.__inactive_player__ + return self._inactive_player def get_opponent(self, player): - """ - Return the opponent of the supplied player. + """Return the opponent of the supplied player. Parameters ---------- @@ -81,30 +80,27 @@ def get_opponent(self, player): this game. Returns - ---------- + ------- object The opponent of the input player object. """ - if player == self.__active_player__: - return self.__inactive_player__ - elif player == self.__inactive_player__: - return self.__active_player__ + if player == self._active_player: + return self._inactive_player + elif player == self._inactive_player: + return self._active_player raise RuntimeError("`player` must be an object registered as a player in the current game.") def copy(self): """ Return a deep copy of the current board. """ - new_board = Board(self.__player_1__, self.__player_2__, width=self.width, height=self.height) + new_board = Board(self._player_1, self._player_2, width=self.width, height=self.height) new_board.move_count = self.move_count - new_board.__active_player__ = self.__active_player__ - new_board.__inactive_player__ = self.__inactive_player__ - new_board.__last_player_move__ = copy(self.__last_player_move__) - new_board.__player_symbols__ = copy(self.__player_symbols__) - new_board.__board_state__ = deepcopy(self.__board_state__) + new_board._active_player = self._active_player + new_board._inactive_player = self._inactive_player + new_board._board_state = copy(self._board_state) return new_board def forecast_move(self, move): - """ - Return a deep copy of the current game with an input move applied to + """Return a deep copy of the current game with an input move applied to advance the game one ply. Parameters @@ -114,8 +110,8 @@ def forecast_move(self, move): the active player on the board. Returns - ---------- - `isolation.Board` + ------- + isolation.Board A deep copy of the board with the input move applied. """ new_board = self.copy() @@ -123,8 +119,7 @@ def forecast_move(self, move): return new_board def move_is_legal(self, move): - """ - Test whether a move is legal in the current game state. + """Test whether a move is legal in the current game state. Parameters ---------- @@ -133,25 +128,22 @@ def move_is_legal(self, move): the active player on the board. Returns - ---------- + ------- bool Returns True if the move is legal, False otherwise """ - row, col = move - return 0 <= row < self.height and \ - 0 <= col < self.width and \ - self.__board_state__[row][col] == Board.BLANK + idx = move[0] + move[1] * self.height + return (0 <= move[0] < self.height and 0 <= move[1] < self.width and + self._board_state[idx] == Board.BLANK) def get_blank_spaces(self): - """ - Return a list of the locations that are still available on the board. + """Return a list of the locations that are still available on the board. """ return [(i, j) for j in range(self.width) for i in range(self.height) - if self.__board_state__[i][j] == Board.BLANK] + if self._board_state[i + j * self.height] == Board.BLANK] def get_player_location(self, player): - """ - Find the current location of the specified player on the board. + """Find the current location of the specified player on the board. Parameters ---------- @@ -159,15 +151,28 @@ def get_player_location(self, player): An object registered as a player in the current game. Returns - ---------- - (int, int) - The coordinate pair (row, column) of the input player. + ------- + (int, int) or None + The coordinate pair (row, column) of the input player, or None + if the player has not moved. """ - return self.__last_player_move__[player] + if player == self._player_1: + if self._board_state[-1] == Board.NOT_MOVED: + return Board.NOT_MOVED + idx = self._board_state[-1] + elif player == self._player_2: + if self._board_state[-2] == Board.NOT_MOVED: + return Board.NOT_MOVED + idx = self._board_state[-2] + else: + raise RuntimeError( + "Invalid player in get_player_location: {}".format(player)) + w = idx // self.height + h = idx % self.height + return (h, w) def get_legal_moves(self, player=None): - """ - Return the list of all legal moves for the specified player. + """Return the list of all legal moves for the specified player. Parameters ---------- @@ -176,46 +181,42 @@ def get_legal_moves(self, player=None): return the legal moves for the active player on the board. Returns - ---------- + ------- list<(int, int)> The list of coordinate pairs (row, column) of all legal moves for the player constrained by the current game state. """ if player is None: player = self.active_player - return self.__get_moves__(self.__last_player_move__[player]) + return self.__get_moves(self.get_player_location(player)) def apply_move(self, move): - """ - Move the active player to a specified location. + """Move the active player to a specified location. Parameters ---------- move : (int, int) A coordinate pair (row, column) indicating the next position for the active player on the board. - - Returns - ---------- - None """ - row, col = move - self.__last_player_move__[self.active_player] = move - self.__board_state__[row][col] = self.__player_symbols__[self.active_player] - self.__active_player__, self.__inactive_player__ = self.__inactive_player__, self.__active_player__ + idx = move[0] + move[1] * self.height + last_move_idx = int(self.active_player == self._player_2) + 1 + self._board_state[-last_move_idx] = idx + self._board_state[idx] = 1 + self._board_state[-3] ^= 1 + self._active_player, self._inactive_player = self._inactive_player, self._active_player self.move_count += 1 def is_winner(self, player): """ Test whether the specified player has won the game. """ - return player == self.inactive_player and not self.get_legal_moves(self.active_player) + return player == self._inactive_player and not self.get_legal_moves(self._active_player) def is_loser(self, player): """ Test whether the specified player has lost the game. """ - return player == self.active_player and not self.get_legal_moves(self.active_player) + return player == self._active_player and not self.get_legal_moves(self._active_player) def utility(self, player): - """ - Returns the utility of the current game state from the perspective + """Returns the utility of the current game state from the perspective of the specified player. / +infinity, "player" wins @@ -236,72 +237,66 @@ def utility(self, player): a value of -inf if the player has lost, and a value of 0 otherwise. """ + if not self.get_legal_moves(self._active_player): - if not self.get_legal_moves(self.active_player): - - if player == self.inactive_player: + if player == self._inactive_player: return float("inf") - if player == self.active_player: + if player == self._active_player: return float("-inf") return 0. - def __get_moves__(self, move): - """ - Generate the list of possible moves for an L-shaped motion (like a + def __get_moves(self, loc): + """Generate the list of possible moves for an L-shaped motion (like a knight in chess). """ - - if move == Board.NOT_MOVED: + if loc == Board.NOT_MOVED: return self.get_blank_spaces() - r, c = move - + r, c = loc directions = [(-2, -1), (-2, 1), (-1, -2), (-1, 2), - (1, -2), (1, 2), (2, -1), (2, 1)] - - valid_moves = [(r+dr,c+dc) for dr, dc in directions if self.move_is_legal((r+dr, c+dc))] - + (1, -2), (1, 2), (2, -1), (2, 1)] + valid_moves = [(r + dr, c + dc) for dr, dc in directions + if self.move_is_legal((r + dr, c + dc))] + random.shuffle(valid_moves) return valid_moves def print_board(self): """DEPRECATED - use Board.to_string()""" return self.to_string() - def to_string(self): + def to_string(self, symbols=['1', '2']): """Generate a string representation of the current game state, marking the location of each player and indicating which cells have been blocked, and which remain open. """ + p1_loc = self._board_state[-1] + p2_loc = self._board_state[-2] - p1_loc = self.__last_player_move__[self.__player_1__] - p2_loc = self.__last_player_move__[self.__player_2__] - - out = '' - + col_margin = len(str(self.height - 1)) + 1 + prefix = "{:<" + "{}".format(col_margin) + "}" + offset = " " * (col_margin + 3) + out = offset + ' '.join(map(str, range(self.width))) + '\n\r' for i in range(self.height): - out += ' | ' - + out += prefix.format(i) + ' | ' for j in range(self.width): - - if not self.__board_state__[i][j]: + idx = i + j * self.height + if not self._board_state[idx]: out += ' ' - elif p1_loc and i == p1_loc[0] and j == p1_loc[1]: - out += '1' - elif p2_loc and i == p2_loc[0] and j == p2_loc[1]: - out += '2' + elif p1_loc == idx: + out += symbols[0] + elif p2_loc == idx: + out += symbols[1] else: out += '-' - out += ' | ' out += '\n\r' return out def play(self, time_limit=TIME_LIMIT_MILLIS): - """ - Execute a match between the players by alternately soliciting them + """Execute a match between the players by alternately soliciting them to select a move and applying it in the game. Parameters @@ -319,33 +314,29 @@ def play(self, time_limit=TIME_LIMIT_MILLIS): """ move_history = [] - curr_time_millis = lambda: 1000 * timeit.default_timer() + time_millis = lambda: 1000 * timeit.default_timer() while True: legal_player_moves = self.get_legal_moves() - game_copy = self.copy() - move_start = curr_time_millis() - time_left = lambda : time_limit - (curr_time_millis() - move_start) - curr_move = self.active_player.get_move(game_copy, legal_player_moves, time_left) + move_start = time_millis() + time_left = lambda : time_limit - (time_millis() - move_start) + curr_move = self._active_player.get_move(game_copy, time_left) move_end = time_left() - # print move_end - if curr_move is None: curr_move = Board.NOT_MOVED - if self.active_player == self.__player_1__: - move_history.append([curr_move]) - else: - move_history[-1].append(curr_move) - if move_end < 0: - return self.__inactive_player__, move_history, "timeout" + return self._inactive_player, move_history, "timeout" if curr_move not in legal_player_moves: - return self.__inactive_player__, move_history, "illegal move" + if len(legal_player_moves) > 0: + return self._inactive_player, move_history, "forfeit" + return self._inactive_player, move_history, "illegal move" + + move_history.append(list(curr_move)) self.apply_move(curr_move) diff --git a/isoviz/LICENSE.txt b/isoviz/LICENSE.txt new file mode 100644 index 00000000..4d3575ea --- /dev/null +++ b/isoviz/LICENSE.txt @@ -0,0 +1,21 @@ +Copyright 2013 Chris Oakman +http://chessboardjs.com/ + +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: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +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. \ No newline at end of file diff --git a/isoviz/css/chessboard.css b/isoviz/css/chessboard.css new file mode 100644 index 00000000..a6686258 --- /dev/null +++ b/isoviz/css/chessboard.css @@ -0,0 +1,91 @@ +/*! + * chessboard.js v0.3.0 + * + * Copyright 2013 Chris Oakman + * Released under the MIT license + * https://github.com/oakmac/chessboardjs/blob/master/LICENSE + * + * Date: 10 Aug 2013 + */ + +td { + font-size: 1.5em; +} + +/* clearfix */ +.clearfix-7da63 { + clear: both; +} + +/* board */ +.board-b72b1 { + border: 2px solid #404040; + -moz-box-sizing: content-box; + box-sizing: content-box; +} + +/* square */ +.square-55d63 { + float: left; + position: relative; + + /* disable any native browser highlighting */ + -webkit-touch-callout: none; + -webkit-user-select: none; + -khtml-user-select: none; + -moz-user-select: none; + -ms-user-select: none; + user-select: none; +} + +/* white square */ +.white-1e1d7.blocked { + background-color: #999999; + color: #333333; +} + +/* black square */ +.black-3c85d.blocked { + background-color: #333333; + color: #999999; +} + +/* white square */ +.white-1e1d7 { + background-color: #f0d9b5; + color: #b58863; +} + +/* black square */ +.black-3c85d { + background-color: #b58863; + color: #f0d9b5; +} + +/* white square */ +.square-55d63.win { + background-color: #339900; + color: #999999; +} + +/* black square */ +.square-55d63.lose { + background-color: #990000; + color: #333333; +} + +/* notation */ +.notation-322f9 { + cursor: default; + font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; + font-size: 14px; + position: absolute; +} +.alpha-d2270 { + bottom: 1px; + right: 3px; +} +.numeric-fc462 { + top: 2px; + left: 2px; +} \ No newline at end of file diff --git a/isoviz/display.html b/isoviz/display.html new file mode 100644 index 00000000..d9d4f899 --- /dev/null +++ b/isoviz/display.html @@ -0,0 +1,139 @@ + + + + + + Empty Board Example + + + + + + +
+
+ Player1:
+ +
+ Player2:
+ +
+ Move History:
+ +
+ +
+
+ +
+
+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/isoviz/img/chesspieces/wikipedia/bN.png b/isoviz/img/chesspieces/wikipedia/bN.png new file mode 100644 index 00000000..e31a6d02 Binary files /dev/null and b/isoviz/img/chesspieces/wikipedia/bN.png differ diff --git a/isoviz/img/chesspieces/wikipedia/wN.png b/isoviz/img/chesspieces/wikipedia/wN.png new file mode 100644 index 00000000..237250c1 Binary files /dev/null and b/isoviz/img/chesspieces/wikipedia/wN.png differ diff --git a/isoviz/js/chessboard.js b/isoviz/js/chessboard.js new file mode 100644 index 00000000..5b85c54e --- /dev/null +++ b/isoviz/js/chessboard.js @@ -0,0 +1,1183 @@ +/*! + * chessboard.js v0.3.0 + * + * Copyright 2013 Chris Oakman + * Released under the MIT license + * http://chessboardjs.com/license + * + * Date: 10 Aug 2013 + */ + +// start anonymous scope +;(function() { +'use strict'; + +//------------------------------------------------------------------------------ +// Chess Util Functions +//------------------------------------------------------------------------------ +var COLUMNS = 'abcdefg'.split(''); + +function validMove(move) { + // move should be a string + if (typeof move !== 'string') return false; + + // move should be in the form of "e2-e4", "f6-d5" + var tmp = move.split('-'); + if (tmp.length !== 2) return false; + + return (validSquare(tmp[0]) === true && validSquare(tmp[1]) === true); +} + +function validSquare(square) { + if (typeof square !== 'string') return false; + return (square.search(/^[a-h][1-7]$/) !== -1); +} + +function validPieceCode(code) { + if (typeof code !== 'string') return false; + return (code.search(/^[bw][N]$/) !== -1); +} + +// TODO: this whole function could probably be replaced with a single regex +function validFen(fen) { + if (typeof fen !== 'string') return false; + + // cut off any move, castling, etc info from the end + // we're only interested in position information + fen = fen.replace(/ .+$/, ''); + + // FEN should be 8 sections separated by slashes + var chunks = fen.split('/'); + if (chunks.length !== 7) return false; + + // check the piece sections + for (var i = 0; i < 7; i++) { + if (chunks[i] === '' || + chunks[i].length > 7 || + chunks[i].search(/[^nN1-7]/) !== -1) { + return false; + } + } + + return true; +} + +function validPositionObject(pos) { + if (typeof pos !== 'object') return false; + + for (var i in pos) { + if (pos.hasOwnProperty(i) !== true) continue; + + if (validSquare(i) !== true || validPieceCode(pos[i]) !== true) { + return false; + } + } + + return true; +} + +// convert FEN piece code to bP, wK, etc +function fenToPieceCode(piece) { + // black piece + if (piece.toLowerCase() === piece) { + return 'b' + piece.toUpperCase(); + } + + // white piece + return 'w' + piece.toUpperCase(); +} + +// convert bP, wK, etc code to FEN structure +function pieceCodeToFen(piece) { + var tmp = piece.split(''); + + // white piece + if (tmp[0] === 'w') { + return tmp[1].toUpperCase(); + } + + // black piece + return tmp[1].toLowerCase(); +} + +// convert FEN string to position object +// returns false if the FEN string is invalid +function fenToObj(fen) { + if (validFen(fen) !== true) { + return false; + } + + // cut off any move, castling, etc info from the end + // we're only interested in position information + fen = fen.replace(/ .+$/, ''); + + var rows = fen.split('/'); + var position = {}; + + var currentRow = 7; + for (var i = 0; i < 7; i++) { + var row = rows[i].split(''); + var colIndex = 0; + + // loop through each character in the FEN section + for (var j = 0; j < row.length; j++) { + // number / empty squares + if (row[j].search(/[1-7]/) !== -1) { + var emptySquares = parseInt(row[j], 10); + colIndex += emptySquares; + } + // piece + else { + var square = COLUMNS[colIndex] + currentRow; + position[square] = fenToPieceCode(row[j]); + colIndex++; + } + } + + currentRow--; + } + + return position; +} + +// position object to FEN string +// returns false if the obj is not a valid position object +function objToFen(obj) { + if (validPositionObject(obj) !== true) { + return false; + } + + var fen = ''; + + var currentRow = 7; + for (var i = 0; i < 7; i++) { + for (var j = 0; j < 7; j++) { + var square = COLUMNS[j] + currentRow; + + // piece exists + if (obj.hasOwnProperty(square) === true) { + fen += pieceCodeToFen(obj[square]); + } + + // empty space + else { + fen += '1'; + } + } + + if (i !== 6) { + fen += '/'; + } + + currentRow--; + } + + // squeeze the numbers together + // haha, I love this solution... + fen = fen.replace(/1111111/g, '7'); + fen = fen.replace(/111111/g, '6'); + fen = fen.replace(/11111/g, '5'); + fen = fen.replace(/1111/g, '4'); + fen = fen.replace(/111/g, '3'); + fen = fen.replace(/11/g, '2'); + + return fen; +} + +window['ChessBoard'] = window['ChessBoard'] || function(containerElOrId, cfg) { +'use strict'; + +cfg = cfg || {}; + +//------------------------------------------------------------------------------ +// Constants +//------------------------------------------------------------------------------ + +var MINIMUM_JQUERY_VERSION = '1.7.0', + START_FEN = '7/7/7/7/7/7/7/7', + START_POSITION = fenToObj(START_FEN); + +// use unique class names to prevent clashing with anything else on the page +// and simplify selectors +var CSS = { + alpha: 'alpha-d2270', + black: 'black-3c85d', + board: 'board-b72b1', + chessboard: 'chessboard-63f37', + clearfix: 'clearfix-7da63', + highlight1: 'highlight1-32417', + highlight2: 'highlight2-9c5d2', + notation: 'notation-322f9', + numeric: 'numeric-fc462', + piece: 'piece-417db', + row: 'row-5277c', + sparePieces: 'spare-pieces-7492f', + sparePiecesBottom: 'spare-pieces-bottom-ae20f', + sparePiecesTop: 'spare-pieces-top-4028b', + square: 'square-55d63', + white: 'white-1e1d7' +}; + +//------------------------------------------------------------------------------ +// Module Scope Variables +//------------------------------------------------------------------------------ + +// DOM elements +var containerEl, + boardEl, + draggedPieceEl, + sparePiecesTopEl, + sparePiecesBottomEl; + +// constructor return object +var widget = {}; + +//------------------------------------------------------------------------------ +// Stateful +//------------------------------------------------------------------------------ + +var ANIMATION_HAPPENING = false, + BOARD_BORDER_SIZE = 2, + CURRENT_ORIENTATION = 'white', + CURRENT_POSITION = {}, + SQUARE_SIZE, + DRAGGED_PIECE, + DRAGGED_PIECE_LOCATION, + DRAGGED_PIECE_SOURCE, + DRAGGING_A_PIECE = false, + SPARE_PIECE_ELS_IDS = {}, + SQUARE_ELS_IDS = {}, + SQUARE_ELS_OFFSETS; + +//------------------------------------------------------------------------------ +// JS Util Functions +//------------------------------------------------------------------------------ + +// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript +function createId() { + return 'xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx-xxxx'.replace(/x/g, function(c) { + var r = Math.random() * 16 | 0; + return r.toString(16); + }); +} + +function deepCopy(thing) { + return JSON.parse(JSON.stringify(thing)); +} + +function parseSemVer(version) { + var tmp = version.split('.'); + return { + major: parseInt(tmp[0], 10), + minor: parseInt(tmp[1], 10), + patch: parseInt(tmp[2], 10) + }; +} + +// returns true if version is >= minimum +function compareSemVer(version, minimum) { + version = parseSemVer(version); + minimum = parseSemVer(minimum); + + var versionNum = (version.major * 10000 * 10000) + + (version.minor * 10000) + version.patch; + var minimumNum = (minimum.major * 10000 * 10000) + + (minimum.minor * 10000) + minimum.patch; + + return (versionNum >= minimumNum); +} + +//------------------------------------------------------------------------------ +// Validation / Errors +//------------------------------------------------------------------------------ + +function error(code, msg, obj) { + // do nothing if showErrors is not set + if (cfg.hasOwnProperty('showErrors') !== true || + cfg.showErrors === false) { + return; + } + + var errorText = 'ChessBoard Error ' + code + ': ' + msg; + + // print to console + if (cfg.showErrors === 'console' && + typeof console === 'object' && + typeof console.log === 'function') { + console.log(errorText); + if (arguments.length >= 2) { + console.log(obj); + } + return; + } + + // alert errors + if (cfg.showErrors === 'alert') { + if (obj) { + errorText += '\n\n' + JSON.stringify(obj); + } + window.alert(errorText); + return; + } + + // custom function + if (typeof cfg.showErrors === 'function') { + cfg.showErrors(code, msg, obj); + } +} + +// check dependencies +function checkDeps() { + // if containerId is a string, it must be the ID of a DOM node + if (typeof containerElOrId === 'string') { + // cannot be empty + if (containerElOrId === '') { + window.alert('ChessBoard Error 1001: ' + + 'The first argument to ChessBoard() cannot be an empty string.' + + '\n\nExiting...'); + return false; + } + + // make sure the container element exists in the DOM + var el = document.getElementById(containerElOrId); + if (! el) { + window.alert('ChessBoard Error 1002: Element with id "' + + containerElOrId + '" does not exist in the DOM.' + + '\n\nExiting...'); + return false; + } + + // set the containerEl + containerEl = $(el); + } + + // else it must be something that becomes a jQuery collection + // with size 1 + // ie: a single DOM node or jQuery object + else { + containerEl = $(containerElOrId); + + if (containerEl.length !== 1) { + window.alert('ChessBoard Error 1003: The first argument to ' + + 'ChessBoard() must be an ID or a single DOM node.' + + '\n\nExiting...'); + return false; + } + } + + // JSON must exist + if (! window.JSON || + typeof JSON.stringify !== 'function' || + typeof JSON.parse !== 'function') { + window.alert('ChessBoard Error 1004: JSON does not exist. ' + + 'Please include a JSON polyfill.\n\nExiting...'); + return false; + } + + // check for a compatible version of jQuery + if (! (typeof window.$ && $.fn && $.fn.jquery && + compareSemVer($.fn.jquery, MINIMUM_JQUERY_VERSION) === true)) { + window.alert('ChessBoard Error 1005: Unable to find a valid version ' + + 'of jQuery. Please include jQuery ' + MINIMUM_JQUERY_VERSION + ' or ' + + 'higher on the page.\n\nExiting...'); + return false; + } + + return true; +} + +function validAnimationSpeed(speed) { + if (speed === 'fast' || speed === 'slow') { + return true; + } + + if ((parseInt(speed, 10) + '') !== (speed + '')) { + return false; + } + + return (speed >= 0); +} + +// validate config / set default options +function expandConfig() { + if (typeof cfg === 'string' || validPositionObject(cfg) === true) { + cfg = { + position: cfg + }; + } + + // default for orientation is white + if (cfg.orientation !== 'black') { + cfg.orientation = 'white'; + } + CURRENT_ORIENTATION = cfg.orientation; + + // default for showNotation is true + if (cfg.showNotation !== false) { + cfg.showNotation = true; + } + + // default for draggable is false + if (cfg.draggable !== true) { + cfg.draggable = false; + } + + // default for dropOffBoard is 'snapback' + if (cfg.dropOffBoard !== 'trash') { + cfg.dropOffBoard = 'snapback'; + } + + // default for sparePieces is false + if (cfg.sparePieces !== true) { + cfg.sparePieces = false; + } + + // draggable must be true if sparePieces is enabled + if (cfg.sparePieces === true) { + cfg.draggable = true; + } + + // default piece theme is wikipedia + if (cfg.hasOwnProperty('pieceTheme') !== true || + (typeof cfg.pieceTheme !== 'string' && + typeof cfg.pieceTheme !== 'function')) { + cfg.pieceTheme = 'img/chesspieces/wikipedia/{piece}.png'; + } + + // animation speeds + if (cfg.hasOwnProperty('appearSpeed') !== true || + validAnimationSpeed(cfg.appearSpeed) !== true) { + cfg.appearSpeed = 200; + } + if (cfg.hasOwnProperty('moveSpeed') !== true || + validAnimationSpeed(cfg.moveSpeed) !== true) { + cfg.moveSpeed = 200; + } + if (cfg.hasOwnProperty('snapbackSpeed') !== true || + validAnimationSpeed(cfg.snapbackSpeed) !== true) { + cfg.snapbackSpeed = 50; + } + if (cfg.hasOwnProperty('snapSpeed') !== true || + validAnimationSpeed(cfg.snapSpeed) !== true) { + cfg.snapSpeed = 25; + } + if (cfg.hasOwnProperty('trashSpeed') !== true || + validAnimationSpeed(cfg.trashSpeed) !== true) { + cfg.trashSpeed = 100; + } + + // make sure position is valid + if (cfg.hasOwnProperty('position') === true) { + if (cfg.position === 'start') { + CURRENT_POSITION = deepCopy(START_POSITION); + } + + else if (validFen(cfg.position) === true) { + CURRENT_POSITION = fenToObj(cfg.position); + } + + else if (validPositionObject(cfg.position) === true) { + CURRENT_POSITION = deepCopy(cfg.position); + } + + else { + error(7263, 'Invalid value passed to config.position.', cfg.position); + } + } + + return true; +} + +//------------------------------------------------------------------------------ +// DOM Misc +//------------------------------------------------------------------------------ + +// calculates square size based on the width of the container +// got a little CSS black magic here, so let me explain: +// get the width of the container element (could be anything), reduce by 1 for +// fudge factor, and then keep reducing until we find an exact mod 8 for +// our square size +function calculateSquareSize() { + var containerWidth = parseInt(containerEl.css('width'), 10); + + // defensive, prevent infinite loop + if (! containerWidth || containerWidth <= 0) { + return 0; + } + + // pad one pixel + var boardWidth = containerWidth - 1; + + while (boardWidth % 7 !== 0 && boardWidth > 0) { + boardWidth--; + } + + return (boardWidth / 7); +} + +// create random IDs for elements +function createElIds() { + // squares on the board + for (var i = 0; i < COLUMNS.length; i++) { + for (var j = 1; j <= 7; j++) { + var square = COLUMNS[i] + j; + SQUARE_ELS_IDS[square] = square + '-' + createId(); + } + } +} + +//------------------------------------------------------------------------------ +// Markup Building +//------------------------------------------------------------------------------ + +function buildBoardContainer() { + var html = '
'; + + html += '
'; + + html += '
'; + + return html; +} + +/* +var buildSquare = function(color, size, id) { + var html = '
'; + + if (cfg.showNotation === true) { + + } + + html += '
'; + + return html; +}; +*/ + +function buildBoard(orientation) { + if (orientation !== 'black') { + orientation = 'white'; + } + + var html = ''; + + // algebraic notation / orientation + var alpha = deepCopy(COLUMNS); + var row = 7; + if (orientation === 'black') { + alpha.reverse(); + row = 1; + } + + var squareColor = 'white'; + for (var i = 0; i < 7; i++) { + html += '
'; + for (var j = 0; j < 7; j++) { + var square = alpha[j] + row; + + html += '
'; + + if (cfg.showNotation === true) { + // alpha notation + if ((orientation === 'white' && row === 1) || + (orientation === 'black' && row === 7)) { + html += '
' + + '
'; + } + + // numeric notation + if (j === 0) { + html += '
' + + '
'; + } + } + + html += '
'; // end .square + + squareColor = (squareColor === 'white' ? 'black' : 'white'); + } + html += '
'; + + squareColor = (squareColor === 'white' ? 'white' : 'black'); + + if (orientation === 'white') { + row--; + } + else { + row++; + } + } + + return html; +} + +function buildPieceImgSrc(piece) { + if (typeof cfg.pieceTheme === 'function') { + return cfg.pieceTheme(piece); + } + + if (typeof cfg.pieceTheme === 'string') { + return cfg.pieceTheme.replace(/{piece}/g, piece); + } + + // NOTE: this should never happen + error(8272, 'Unable to build image source for cfg.pieceTheme.'); + return ''; +} + +function buildPiece(piece, hidden, id) { + var html = ''; + + return html; +} + +//------------------------------------------------------------------------------ +// Animations +//------------------------------------------------------------------------------ + +function animateSquareToSquare(src, dest, piece, completeFn) { + // get information about the source and destination squares + var srcSquareEl = $('#' + SQUARE_ELS_IDS[src]); + var srcSquarePosition = srcSquareEl.offset(); + var destSquareEl = $('#' + SQUARE_ELS_IDS[dest]); + var destSquarePosition = destSquareEl.offset(); + + // create the animated piece and absolutely position it + // over the source square + var animatedPieceId = createId(); + $('body').append(buildPiece(piece, true, animatedPieceId)); + var animatedPieceEl = $('#' + animatedPieceId); + animatedPieceEl.css({ + display: '', + position: 'absolute', + top: srcSquarePosition.top, + left: srcSquarePosition.left + }); + + // remove original piece from source square + srcSquareEl.find('.' + CSS.piece).remove(); + + // on complete + var complete = function() { + // add the "real" piece to the destination square + destSquareEl.append(buildPiece(piece)); + + // remove the animated piece + animatedPieceEl.remove(); + + // run complete function + if (typeof completeFn === 'function') { + completeFn(); + } + }; + + // animate the piece to the destination square + var opts = { + duration: cfg.moveSpeed, + complete: complete + }; + animatedPieceEl.animate(destSquarePosition, opts); +} + +// execute an array of animations +function doAnimations(a, oldPos, newPos) { + ANIMATION_HAPPENING = true; + + var numFinished = 0; + function onFinish() { + numFinished++; + + // exit if all the animations aren't finished + if (numFinished !== a.length) return; + + drawPositionInstant(); + ANIMATION_HAPPENING = false; + + // run their onMoveEnd function + if (cfg.hasOwnProperty('onMoveEnd') === true && + typeof cfg.onMoveEnd === 'function') { + cfg.onMoveEnd(deepCopy(oldPos), deepCopy(newPos)); + } + } + + for (var i = 0; i < a.length; i++) { + // clear a piece + if (a[i].type === 'clear') { + $('#' + SQUARE_ELS_IDS[a[i].square] + ' .' + CSS.piece) + .fadeOut(cfg.trashSpeed, onFinish); + } + + // add a piece (no spare pieces) + if (a[i].type === 'add' && cfg.sparePieces !== true) { + $('#' + SQUARE_ELS_IDS[a[i].square]) + .append(buildPiece(a[i].piece, true)) + .find('.' + CSS.piece) + .fadeIn(cfg.appearSpeed, onFinish); + } + + // add a piece from a spare piece + if (a[i].type === 'add' && cfg.sparePieces === true) { + animateSparePieceToSquare(a[i].piece, a[i].square, onFinish); + } + + // move a piece + if (a[i].type === 'move') { + animateSquareToSquare(a[i].source, a[i].destination, a[i].piece, + onFinish); + } + } +} + +// returns the distance between two squares +function squareDistance(s1, s2) { + s1 = s1.split(''); + var s1x = COLUMNS.indexOf(s1[0]) + 1; + var s1y = parseInt(s1[1], 10); + + s2 = s2.split(''); + var s2x = COLUMNS.indexOf(s2[0]) + 1; + var s2y = parseInt(s2[1], 10); + + var xDelta = Math.abs(s1x - s2x); + var yDelta = Math.abs(s1y - s2y); + + if (xDelta >= yDelta) return xDelta; + return yDelta; +} + +// returns an array of closest squares from square +function createRadius(square) { + var squares = []; + + // calculate distance of all squares + for (var i = 0; i < 7; i++) { + for (var j = 0; j < 7; j++) { + var s = COLUMNS[i] + (j + 1); + + // skip the square we're starting from + if (square === s) continue; + + squares.push({ + square: s, + distance: squareDistance(square, s) + }); + } + } + + // sort by distance + squares.sort(function(a, b) { + return a.distance - b.distance; + }); + + // just return the square code + var squares2 = []; + for (var i = 0; i < squares.length; i++) { + squares2.push(squares[i].square); + } + + return squares2; +} + +// returns the square of the closest instance of piece +// returns false if no instance of piece is found in position +function findClosestPiece(position, piece, square) { + // create array of closest squares from square + var closestSquares = createRadius(square); + + // search through the position in order of distance for the piece + for (var i = 0; i < closestSquares.length; i++) { + var s = closestSquares[i]; + + if (position.hasOwnProperty(s) === true && position[s] === piece) { + return s; + } + } + + return false; +} + +// calculate an array of animations that need to happen in order to get +// from pos1 to pos2 +function calculateAnimations(pos1, pos2) { + // make copies of both + pos1 = deepCopy(pos1); + pos2 = deepCopy(pos2); + + var animations = []; + var squaresMovedTo = {}; + + // remove pieces that are the same in both positions + for (var i in pos2) { + if (pos2.hasOwnProperty(i) !== true) continue; + + if (pos1.hasOwnProperty(i) === true && pos1[i] === pos2[i]) { + delete pos1[i]; + delete pos2[i]; + } + } + + // find all the "move" animations + for (var i in pos2) { + if (pos2.hasOwnProperty(i) !== true) continue; + + var closestPiece = findClosestPiece(pos1, pos2[i], i); + if (closestPiece !== false) { + animations.push({ + type: 'move', + source: closestPiece, + destination: i, + piece: pos2[i] + }); + + delete pos1[closestPiece]; + delete pos2[i]; + squaresMovedTo[i] = true; + } + } + + // add pieces to pos2 + for (var i in pos2) { + if (pos2.hasOwnProperty(i) !== true) continue; + + animations.push({ + type: 'add', + square: i, + piece: pos2[i] + }) + + delete pos2[i]; + } + + // clear pieces from pos1 + for (var i in pos1) { + if (pos1.hasOwnProperty(i) !== true) continue; + + // do not clear a piece if it is on a square that is the result + // of a "move", ie: a piece capture + if (squaresMovedTo.hasOwnProperty(i) === true) continue; + + animations.push({ + type: 'clear', + square: i, + piece: pos1[i] + }); + + delete pos1[i]; + } + + return animations; +} + +//------------------------------------------------------------------------------ +// Control Flow +//------------------------------------------------------------------------------ + +function drawPositionInstant() { + // clear the board + boardEl.find('.' + CSS.piece).remove(); + + // add the pieces + for (var i in CURRENT_POSITION) { + if (CURRENT_POSITION.hasOwnProperty(i) !== true) continue; + + $('#' + SQUARE_ELS_IDS[i]).append(buildPiece(CURRENT_POSITION[i])); + } +} + +function drawBoard() { + boardEl.html(buildBoard(CURRENT_ORIENTATION)); + drawPositionInstant(); +} + +// given a position and a set of moves, return a new position +// with the moves executed +function calculatePositionFromMoves(position, moves) { + position = deepCopy(position); + + for (var i in moves) { + if (moves.hasOwnProperty(i) !== true) continue; + + // skip the move if the position doesn't have a piece on the source square + if (position.hasOwnProperty(i) !== true) continue; + + var piece = position[i]; + delete position[i]; + position[moves[i]] = piece; + } + + return position; +} + +function setCurrentPosition(position) { + var oldPos = deepCopy(CURRENT_POSITION); + var newPos = deepCopy(position); + var oldFen = objToFen(oldPos); + var newFen = objToFen(newPos); + + // do nothing if no change in position + if (oldFen === newFen) return; + + // run their onChange function + if (cfg.hasOwnProperty('onChange') === true && + typeof cfg.onChange === 'function') { + cfg.onChange(oldPos, newPos); + } + + // update state + CURRENT_POSITION = position; +} + +function isXYOnSquare(x, y) { + for (var i in SQUARE_ELS_OFFSETS) { + if (SQUARE_ELS_OFFSETS.hasOwnProperty(i) !== true) continue; + + var s = SQUARE_ELS_OFFSETS[i]; + if (x >= s.left && x < s.left + SQUARE_SIZE && + y >= s.top && y < s.top + SQUARE_SIZE) { + return i; + } + } + + return 'offboard'; +} + +// records the XY coords of every square into memory +function captureSquareOffsets() { + SQUARE_ELS_OFFSETS = {}; + + for (var i in SQUARE_ELS_IDS) { + if (SQUARE_ELS_IDS.hasOwnProperty(i) !== true) continue; + + SQUARE_ELS_OFFSETS[i] = $('#' + SQUARE_ELS_IDS[i]).offset(); + } +} + +function removeSquareHighlights() { + boardEl.find('.' + CSS.square) + .removeClass(CSS.highlight1 + ' ' + CSS.highlight2); +} + +//------------------------------------------------------------------------------ +// Public Methods +//------------------------------------------------------------------------------ + +// clear the board +widget.clear = function(useAnimation) { + widget.position({}, useAnimation); +}; + +/* +// get or set config properties +// TODO: write this, GitHub Issue #1 +widget.config = function(arg1, arg2) { + // get the current config + if (arguments.length === 0) { + return deepCopy(cfg); + } +}; +*/ + +// remove the widget from the page +widget.destroy = function() { + // remove markup + containerEl.html(''); + draggedPieceEl.remove(); + + // remove event handlers + containerEl.unbind(); +}; + +// flip orientation +widget.flip = function() { + widget.orientation('flip'); +}; + +/* +// TODO: write this, GitHub Issue #5 +widget.highlight = function() { + +}; +*/ + +// move pieces +widget.move = function() { + // no need to throw an error here; just do nothing + if (arguments.length === 0) return; + + var useAnimation = true; + + // collect the moves into an object + var moves = {}; + for (var i = 0; i < arguments.length; i++) { + // any "false" to this function means no animations + if (arguments[i] === false) { + useAnimation = false; + continue; + } + + // skip invalid arguments + if (validMove(arguments[i]) !== true) { + error(2826, 'Invalid move passed to the move method.', arguments[i]); + continue; + } + + var tmp = arguments[i].split('-'); + moves[tmp[0]] = tmp[1]; + } + + // calculate position from moves + var newPos = calculatePositionFromMoves(CURRENT_POSITION, moves); + + // update the board + widget.position(newPos, useAnimation); + + // block the square + for (var m in moves) { + var el = document.getElementById(SQUARE_ELS_IDS[m]); + el.classList.add("blocked"); + } + + // return the new position object + return newPos; +}; + +widget.position = function(position, useAnimation) { + // no arguments, return the current position + if (arguments.length === 0) { + return deepCopy(CURRENT_POSITION); + } + + // get position as FEN + if (typeof position === 'string' && position.toLowerCase() === 'fen') { + return objToFen(CURRENT_POSITION); + } + + // default for useAnimations is true + if (useAnimation !== false) { + useAnimation = true; + } + + // start position + if (typeof position === 'string' && position.toLowerCase() === 'start') { + position = deepCopy(START_POSITION); + } + + // convert FEN to position object + if (validFen(position) === true) { + position = fenToObj(position); + } + + // validate position object + if (validPositionObject(position) !== true) { + error(6482, 'Invalid value passed to the position method.', position); + return; + } + + if (useAnimation === true) { + // start the animations + doAnimations(calculateAnimations(CURRENT_POSITION, position), + CURRENT_POSITION, position); + + // set the new position + setCurrentPosition(position); + } + // instant update + else { + setCurrentPosition(position); + drawPositionInstant(); + } +}; + +widget.resize = function() { + // calulate the new square size + SQUARE_SIZE = calculateSquareSize(); + + // set board width + boardEl.css('width', (SQUARE_SIZE * 7) + 'px'); + + // redraw the board + drawBoard(); +}; + +widget.finalize = function(winCell, lossCell) { + + var el; + + el = document.getElementById(SQUARE_ELS_IDS[winCell]); + el.classList.add("win"); + + el = document.getElementById(SQUARE_ELS_IDS[lossCell]); + el.classList.add("lose"); +}; + +// set the starting position +widget.start = function(useAnimation) { + widget.position('start', useAnimation); +}; + +//------------------------------------------------------------------------------ +// Browser Events +//------------------------------------------------------------------------------ + + +function stopDefault(e) { + e.preventDefault(); +} + +//------------------------------------------------------------------------------ +// Initialization +//------------------------------------------------------------------------------ + +function initDom() { + // build board and save it in memory + containerEl.html(buildBoardContainer()); + boardEl = containerEl.find('.' + CSS.board); + + // get the border size + BOARD_BORDER_SIZE = parseInt(boardEl.css('borderLeftWidth'), 10); + + // set the size and draw the board + widget.resize(); +} + +function init() { + if (checkDeps() !== true || + expandConfig() !== true) return; + + // create unique IDs for all the elements we will create + createElIds(); + + initDom(); +} + +// go time +init(); + +// return the widget object +return widget; + +}; // end window.ChessBoard + +// expose util functions +window.ChessBoard.fenToObj = fenToObj; +window.ChessBoard.objToFen = objToFen; + +})(); // end anonymous wrapper diff --git a/isoviz/js/jquery-1.10.1.min.js b/isoviz/js/jquery-1.10.1.min.js new file mode 100644 index 00000000..c3468613 --- /dev/null +++ b/isoviz/js/jquery-1.10.1.min.js @@ -0,0 +1,4 @@ +/*! jQuery v1.10.1 | (c) 2005, 2013 jQuery Foundation, Inc. | jquery.org/license */ +(function(e,t){var n,r,i=typeof t,o=e.location,a=e.document,s=a.documentElement,l=e.jQuery,u=e.$,c={},p=[],f="1.10.1",d=p.concat,h=p.push,g=p.slice,m=p.indexOf,y=c.toString,v=c.hasOwnProperty,b=f.trim,x=function(e,t){return new x.fn.init(e,t,r)},w=/[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,T=/\S+/g,C=/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,N=/^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,k=/^<(\w+)\s*\/?>(?:<\/\1>|)$/,E=/^[\],:{}\s]*$/,S=/(?:^|:|,)(?:\s*\[)+/g,A=/\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,j=/"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,D=/^-ms-/,L=/-([\da-z])/gi,H=function(e,t){return t.toUpperCase()},q=function(e){(a.addEventListener||"load"===e.type||"complete"===a.readyState)&&(_(),x.ready())},_=function(){a.addEventListener?(a.removeEventListener("DOMContentLoaded",q,!1),e.removeEventListener("load",q,!1)):(a.detachEvent("onreadystatechange",q),e.detachEvent("onload",q))};x.fn=x.prototype={jquery:f,constructor:x,init:function(e,n,r){var i,o;if(!e)return this;if("string"==typeof e){if(i="<"===e.charAt(0)&&">"===e.charAt(e.length-1)&&e.length>=3?[null,e,null]:N.exec(e),!i||!i[1]&&n)return!n||n.jquery?(n||r).find(e):this.constructor(n).find(e);if(i[1]){if(n=n instanceof x?n[0]:n,x.merge(this,x.parseHTML(i[1],n&&n.nodeType?n.ownerDocument||n:a,!0)),k.test(i[1])&&x.isPlainObject(n))for(i in n)x.isFunction(this[i])?this[i](n[i]):this.attr(i,n[i]);return this}if(o=a.getElementById(i[2]),o&&o.parentNode){if(o.id!==i[2])return r.find(e);this.length=1,this[0]=o}return this.context=a,this.selector=e,this}return e.nodeType?(this.context=this[0]=e,this.length=1,this):x.isFunction(e)?r.ready(e):(e.selector!==t&&(this.selector=e.selector,this.context=e.context),x.makeArray(e,this))},selector:"",length:0,toArray:function(){return g.call(this)},get:function(e){return null==e?this.toArray():0>e?this[this.length+e]:this[e]},pushStack:function(e){var t=x.merge(this.constructor(),e);return t.prevObject=this,t.context=this.context,t},each:function(e,t){return x.each(this,e,t)},ready:function(e){return x.ready.promise().done(e),this},slice:function(){return this.pushStack(g.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},map:function(e){return this.pushStack(x.map(this,function(t,n){return e.call(t,n,t)}))},end:function(){return this.prevObject||this.constructor(null)},push:h,sort:[].sort,splice:[].splice},x.fn.init.prototype=x.fn,x.extend=x.fn.extend=function(){var e,n,r,i,o,a,s=arguments[0]||{},l=1,u=arguments.length,c=!1;for("boolean"==typeof s&&(c=s,s=arguments[1]||{},l=2),"object"==typeof s||x.isFunction(s)||(s={}),u===l&&(s=this,--l);u>l;l++)if(null!=(o=arguments[l]))for(i in o)e=s[i],r=o[i],s!==r&&(c&&r&&(x.isPlainObject(r)||(n=x.isArray(r)))?(n?(n=!1,a=e&&x.isArray(e)?e:[]):a=e&&x.isPlainObject(e)?e:{},s[i]=x.extend(c,a,r)):r!==t&&(s[i]=r));return s},x.extend({expando:"jQuery"+(f+Math.random()).replace(/\D/g,""),noConflict:function(t){return e.$===x&&(e.$=u),t&&e.jQuery===x&&(e.jQuery=l),x},isReady:!1,readyWait:1,holdReady:function(e){e?x.readyWait++:x.ready(!0)},ready:function(e){if(e===!0?!--x.readyWait:!x.isReady){if(!a.body)return setTimeout(x.ready);x.isReady=!0,e!==!0&&--x.readyWait>0||(n.resolveWith(a,[x]),x.fn.trigger&&x(a).trigger("ready").off("ready"))}},isFunction:function(e){return"function"===x.type(e)},isArray:Array.isArray||function(e){return"array"===x.type(e)},isWindow:function(e){return null!=e&&e==e.window},isNumeric:function(e){return!isNaN(parseFloat(e))&&isFinite(e)},type:function(e){return null==e?e+"":"object"==typeof e||"function"==typeof e?c[y.call(e)]||"object":typeof e},isPlainObject:function(e){var n;if(!e||"object"!==x.type(e)||e.nodeType||x.isWindow(e))return!1;try{if(e.constructor&&!v.call(e,"constructor")&&!v.call(e.constructor.prototype,"isPrototypeOf"))return!1}catch(r){return!1}if(x.support.ownLast)for(n in e)return v.call(e,n);for(n in e);return n===t||v.call(e,n)},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},error:function(e){throw Error(e)},parseHTML:function(e,t,n){if(!e||"string"!=typeof e)return null;"boolean"==typeof t&&(n=t,t=!1),t=t||a;var r=k.exec(e),i=!n&&[];return r?[t.createElement(r[1])]:(r=x.buildFragment([e],t,i),i&&x(i).remove(),x.merge([],r.childNodes))},parseJSON:function(n){return e.JSON&&e.JSON.parse?e.JSON.parse(n):null===n?n:"string"==typeof n&&(n=x.trim(n),n&&E.test(n.replace(A,"@").replace(j,"]").replace(S,"")))?Function("return "+n)():(x.error("Invalid JSON: "+n),t)},parseXML:function(n){var r,i;if(!n||"string"!=typeof n)return null;try{e.DOMParser?(i=new DOMParser,r=i.parseFromString(n,"text/xml")):(r=new ActiveXObject("Microsoft.XMLDOM"),r.async="false",r.loadXML(n))}catch(o){r=t}return r&&r.documentElement&&!r.getElementsByTagName("parsererror").length||x.error("Invalid XML: "+n),r},noop:function(){},globalEval:function(t){t&&x.trim(t)&&(e.execScript||function(t){e.eval.call(e,t)})(t)},camelCase:function(e){return e.replace(D,"ms-").replace(L,H)},nodeName:function(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()},each:function(e,t,n){var r,i=0,o=e.length,a=M(e);if(n){if(a){for(;o>i;i++)if(r=t.apply(e[i],n),r===!1)break}else for(i in e)if(r=t.apply(e[i],n),r===!1)break}else if(a){for(;o>i;i++)if(r=t.call(e[i],i,e[i]),r===!1)break}else for(i in e)if(r=t.call(e[i],i,e[i]),r===!1)break;return e},trim:b&&!b.call("\ufeff\u00a0")?function(e){return null==e?"":b.call(e)}:function(e){return null==e?"":(e+"").replace(C,"")},makeArray:function(e,t){var n=t||[];return null!=e&&(M(Object(e))?x.merge(n,"string"==typeof e?[e]:e):h.call(n,e)),n},inArray:function(e,t,n){var r;if(t){if(m)return m.call(t,e,n);for(r=t.length,n=n?0>n?Math.max(0,r+n):n:0;r>n;n++)if(n in t&&t[n]===e)return n}return-1},merge:function(e,n){var r=n.length,i=e.length,o=0;if("number"==typeof r)for(;r>o;o++)e[i++]=n[o];else while(n[o]!==t)e[i++]=n[o++];return e.length=i,e},grep:function(e,t,n){var r,i=[],o=0,a=e.length;for(n=!!n;a>o;o++)r=!!t(e[o],o),n!==r&&i.push(e[o]);return i},map:function(e,t,n){var r,i=0,o=e.length,a=M(e),s=[];if(a)for(;o>i;i++)r=t(e[i],i,n),null!=r&&(s[s.length]=r);else for(i in e)r=t(e[i],i,n),null!=r&&(s[s.length]=r);return d.apply([],s)},guid:1,proxy:function(e,n){var r,i,o;return"string"==typeof n&&(o=e[n],n=e,e=o),x.isFunction(e)?(r=g.call(arguments,2),i=function(){return e.apply(n||this,r.concat(g.call(arguments)))},i.guid=e.guid=e.guid||x.guid++,i):t},access:function(e,n,r,i,o,a,s){var l=0,u=e.length,c=null==r;if("object"===x.type(r)){o=!0;for(l in r)x.access(e,n,l,r[l],!0,a,s)}else if(i!==t&&(o=!0,x.isFunction(i)||(s=!0),c&&(s?(n.call(e,i),n=null):(c=n,n=function(e,t,n){return c.call(x(e),n)})),n))for(;u>l;l++)n(e[l],r,s?i:i.call(e[l],l,n(e[l],r)));return o?e:c?n.call(e):u?n(e[0],r):a},now:function(){return(new Date).getTime()},swap:function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];i=n.apply(e,r||[]);for(o in t)e.style[o]=a[o];return i}}),x.ready.promise=function(t){if(!n)if(n=x.Deferred(),"complete"===a.readyState)setTimeout(x.ready);else if(a.addEventListener)a.addEventListener("DOMContentLoaded",q,!1),e.addEventListener("load",q,!1);else{a.attachEvent("onreadystatechange",q),e.attachEvent("onload",q);var r=!1;try{r=null==e.frameElement&&a.documentElement}catch(i){}r&&r.doScroll&&function o(){if(!x.isReady){try{r.doScroll("left")}catch(e){return setTimeout(o,50)}_(),x.ready()}}()}return n.promise(t)},x.each("Boolean Number String Function Array Date RegExp Object Error".split(" "),function(e,t){c["[object "+t+"]"]=t.toLowerCase()});function M(e){var t=e.length,n=x.type(e);return x.isWindow(e)?!1:1===e.nodeType&&t?!0:"array"===n||"function"!==n&&(0===t||"number"==typeof t&&t>0&&t-1 in e)}r=x(a),function(e,t){var n,r,i,o,a,s,l,u,c,p,f,d,h,g,m,y,v,b="sizzle"+-new Date,w=e.document,T=0,C=0,N=lt(),k=lt(),E=lt(),S=!1,A=function(){return 0},j=typeof t,D=1<<31,L={}.hasOwnProperty,H=[],q=H.pop,_=H.push,M=H.push,O=H.slice,F=H.indexOf||function(e){var t=0,n=this.length;for(;n>t;t++)if(this[t]===e)return t;return-1},B="checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",P="[\\x20\\t\\r\\n\\f]",R="(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",W=R.replace("w","w#"),$="\\["+P+"*("+R+")"+P+"*(?:([*^$|!~]?=)"+P+"*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|("+W+")|)|)"+P+"*\\]",I=":("+R+")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|"+$.replace(3,8)+")*)|.*)\\)|)",z=RegExp("^"+P+"+|((?:^|[^\\\\])(?:\\\\.)*)"+P+"+$","g"),X=RegExp("^"+P+"*,"+P+"*"),U=RegExp("^"+P+"*([>+~]|"+P+")"+P+"*"),V=RegExp(P+"*[+~]"),Y=RegExp("="+P+"*([^\\]'\"]*)"+P+"*\\]","g"),J=RegExp(I),G=RegExp("^"+W+"$"),Q={ID:RegExp("^#("+R+")"),CLASS:RegExp("^\\.("+R+")"),TAG:RegExp("^("+R.replace("w","w*")+")"),ATTR:RegExp("^"+$),PSEUDO:RegExp("^"+I),CHILD:RegExp("^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\("+P+"*(even|odd|(([+-]|)(\\d*)n|)"+P+"*(?:([+-]|)"+P+"*(\\d+)|))"+P+"*\\)|)","i"),bool:RegExp("^(?:"+B+")$","i"),needsContext:RegExp("^"+P+"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\("+P+"*((?:-\\d)?\\d*)"+P+"*\\)|)(?=[^-]|$)","i")},K=/^[^{]+\{\s*\[native \w/,Z=/^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,et=/^(?:input|select|textarea|button)$/i,tt=/^h\d$/i,nt=/'|\\/g,rt=RegExp("\\\\([\\da-f]{1,6}"+P+"?|("+P+")|.)","ig"),it=function(e,t,n){var r="0x"+t-65536;return r!==r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(55296|r>>10,56320|1023&r)};try{M.apply(H=O.call(w.childNodes),w.childNodes),H[w.childNodes.length].nodeType}catch(ot){M={apply:H.length?function(e,t){_.apply(e,O.call(t))}:function(e,t){var n=e.length,r=0;while(e[n++]=t[r++]);e.length=n-1}}}function at(e,t,n,i){var o,a,s,l,u,c,d,m,y,x;if((t?t.ownerDocument||t:w)!==f&&p(t),t=t||f,n=n||[],!e||"string"!=typeof e)return n;if(1!==(l=t.nodeType)&&9!==l)return[];if(h&&!i){if(o=Z.exec(e))if(s=o[1]){if(9===l){if(a=t.getElementById(s),!a||!a.parentNode)return n;if(a.id===s)return n.push(a),n}else if(t.ownerDocument&&(a=t.ownerDocument.getElementById(s))&&v(t,a)&&a.id===s)return n.push(a),n}else{if(o[2])return M.apply(n,t.getElementsByTagName(e)),n;if((s=o[3])&&r.getElementsByClassName&&t.getElementsByClassName)return M.apply(n,t.getElementsByClassName(s)),n}if(r.qsa&&(!g||!g.test(e))){if(m=d=b,y=t,x=9===l&&e,1===l&&"object"!==t.nodeName.toLowerCase()){c=bt(e),(d=t.getAttribute("id"))?m=d.replace(nt,"\\$&"):t.setAttribute("id",m),m="[id='"+m+"'] ",u=c.length;while(u--)c[u]=m+xt(c[u]);y=V.test(e)&&t.parentNode||t,x=c.join(",")}if(x)try{return M.apply(n,y.querySelectorAll(x)),n}catch(T){}finally{d||t.removeAttribute("id")}}}return At(e.replace(z,"$1"),t,n,i)}function st(e){return K.test(e+"")}function lt(){var e=[];function t(n,r){return e.push(n+=" ")>o.cacheLength&&delete t[e.shift()],t[n]=r}return t}function ut(e){return e[b]=!0,e}function ct(e){var t=f.createElement("div");try{return!!e(t)}catch(n){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function pt(e,t,n){e=e.split("|");var r,i=e.length,a=n?null:t;while(i--)(r=o.attrHandle[e[i]])&&r!==t||(o.attrHandle[e[i]]=a)}function ft(e,t){var n=e.getAttributeNode(t);return n&&n.specified?n.value:e[t]===!0?t.toLowerCase():null}function dt(e,t){return e.getAttribute(t,"type"===t.toLowerCase()?1:2)}function ht(e){return"input"===e.nodeName.toLowerCase()?e.defaultValue:t}function gt(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&(~t.sourceIndex||D)-(~e.sourceIndex||D);if(r)return r;if(n)while(n=n.nextSibling)if(n===t)return-1;return e?1:-1}function mt(e){return function(t){var n=t.nodeName.toLowerCase();return"input"===n&&t.type===e}}function yt(e){return function(t){var n=t.nodeName.toLowerCase();return("input"===n||"button"===n)&&t.type===e}}function vt(e){return ut(function(t){return t=+t,ut(function(n,r){var i,o=e([],n.length,t),a=o.length;while(a--)n[i=o[a]]&&(n[i]=!(r[i]=n[i]))})})}s=at.isXML=function(e){var t=e&&(e.ownerDocument||e).documentElement;return t?"HTML"!==t.nodeName:!1},r=at.support={},p=at.setDocument=function(e){var n=e?e.ownerDocument||e:w,i=n.parentWindow;return n!==f&&9===n.nodeType&&n.documentElement?(f=n,d=n.documentElement,h=!s(n),i&&i.frameElement&&i.attachEvent("onbeforeunload",function(){p()}),r.attributes=ct(function(e){return e.innerHTML="",pt("type|href|height|width",dt,"#"===e.firstChild.getAttribute("href")),pt(B,ft,null==e.getAttribute("disabled")),e.className="i",!e.getAttribute("className")}),r.input=ct(function(e){return e.innerHTML="",e.firstChild.setAttribute("value",""),""===e.firstChild.getAttribute("value")}),pt("value",ht,r.attributes&&r.input),r.getElementsByTagName=ct(function(e){return e.appendChild(n.createComment("")),!e.getElementsByTagName("*").length}),r.getElementsByClassName=ct(function(e){return e.innerHTML="
",e.firstChild.className="i",2===e.getElementsByClassName("i").length}),r.getById=ct(function(e){return d.appendChild(e).id=b,!n.getElementsByName||!n.getElementsByName(b).length}),r.getById?(o.find.ID=function(e,t){if(typeof t.getElementById!==j&&h){var n=t.getElementById(e);return n&&n.parentNode?[n]:[]}},o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){return e.getAttribute("id")===t}}):(delete o.find.ID,o.filter.ID=function(e){var t=e.replace(rt,it);return function(e){var n=typeof e.getAttributeNode!==j&&e.getAttributeNode("id");return n&&n.value===t}}),o.find.TAG=r.getElementsByTagName?function(e,n){return typeof n.getElementsByTagName!==j?n.getElementsByTagName(e):t}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if("*"===e){while(n=o[i++])1===n.nodeType&&r.push(n);return r}return o},o.find.CLASS=r.getElementsByClassName&&function(e,n){return typeof n.getElementsByClassName!==j&&h?n.getElementsByClassName(e):t},m=[],g=[],(r.qsa=st(n.querySelectorAll))&&(ct(function(e){e.innerHTML="",e.querySelectorAll("[selected]").length||g.push("\\["+P+"*(?:value|"+B+")"),e.querySelectorAll(":checked").length||g.push(":checked")}),ct(function(e){var t=n.createElement("input");t.setAttribute("type","hidden"),e.appendChild(t).setAttribute("t",""),e.querySelectorAll("[t^='']").length&&g.push("[*^$]="+P+"*(?:''|\"\")"),e.querySelectorAll(":enabled").length||g.push(":enabled",":disabled"),e.querySelectorAll("*,:x"),g.push(",.*:")})),(r.matchesSelector=st(y=d.webkitMatchesSelector||d.mozMatchesSelector||d.oMatchesSelector||d.msMatchesSelector))&&ct(function(e){r.disconnectedMatch=y.call(e,"div"),y.call(e,"[s!='']:x"),m.push("!=",I)}),g=g.length&&RegExp(g.join("|")),m=m.length&&RegExp(m.join("|")),v=st(d.contains)||d.compareDocumentPosition?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)while(t=t.parentNode)if(t===e)return!0;return!1},r.sortDetached=ct(function(e){return 1&e.compareDocumentPosition(n.createElement("div"))}),A=d.compareDocumentPosition?function(e,t){if(e===t)return S=!0,0;var i=t.compareDocumentPosition&&e.compareDocumentPosition&&e.compareDocumentPosition(t);return i?1&i||!r.sortDetached&&t.compareDocumentPosition(e)===i?e===n||v(w,e)?-1:t===n||v(w,t)?1:c?F.call(c,e)-F.call(c,t):0:4&i?-1:1:e.compareDocumentPosition?-1:1}:function(e,t){var r,i=0,o=e.parentNode,a=t.parentNode,s=[e],l=[t];if(e===t)return S=!0,0;if(!o||!a)return e===n?-1:t===n?1:o?-1:a?1:c?F.call(c,e)-F.call(c,t):0;if(o===a)return gt(e,t);r=e;while(r=r.parentNode)s.unshift(r);r=t;while(r=r.parentNode)l.unshift(r);while(s[i]===l[i])i++;return i?gt(s[i],l[i]):s[i]===w?-1:l[i]===w?1:0},n):f},at.matches=function(e,t){return at(e,null,null,t)},at.matchesSelector=function(e,t){if((e.ownerDocument||e)!==f&&p(e),t=t.replace(Y,"='$1']"),!(!r.matchesSelector||!h||m&&m.test(t)||g&&g.test(t)))try{var n=y.call(e,t);if(n||r.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(i){}return at(t,f,null,[e]).length>0},at.contains=function(e,t){return(e.ownerDocument||e)!==f&&p(e),v(e,t)},at.attr=function(e,n){(e.ownerDocument||e)!==f&&p(e);var i=o.attrHandle[n.toLowerCase()],a=i&&L.call(o.attrHandle,n.toLowerCase())?i(e,n,!h):t;return a===t?r.attributes||!h?e.getAttribute(n):(a=e.getAttributeNode(n))&&a.specified?a.value:null:a},at.error=function(e){throw Error("Syntax error, unrecognized expression: "+e)},at.uniqueSort=function(e){var t,n=[],i=0,o=0;if(S=!r.detectDuplicates,c=!r.sortStable&&e.slice(0),e.sort(A),S){while(t=e[o++])t===e[o]&&(i=n.push(o));while(i--)e.splice(n[i],1)}return e},a=at.getText=function(e){var t,n="",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if("string"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=a(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r];r++)n+=a(t);return n},o=at.selectors={cacheLength:50,createPseudo:ut,match:Q,attrHandle:{},find:{},relative:{">":{dir:"parentNode",first:!0}," ":{dir:"parentNode"},"+":{dir:"previousSibling",first:!0},"~":{dir:"previousSibling"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(rt,it),e[3]=(e[4]||e[5]||"").replace(rt,it),"~="===e[2]&&(e[3]=" "+e[3]+" "),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),"nth"===e[1].slice(0,3)?(e[3]||at.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*("even"===e[3]||"odd"===e[3])),e[5]=+(e[7]+e[8]||"odd"===e[3])):e[3]&&at.error(e[0]),e},PSEUDO:function(e){var n,r=!e[5]&&e[2];return Q.CHILD.test(e[0])?null:(e[3]&&e[4]!==t?e[2]=e[4]:r&&J.test(r)&&(n=bt(r,!0))&&(n=r.indexOf(")",r.length-n)-r.length)&&(e[0]=e[0].slice(0,n),e[2]=r.slice(0,n)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(rt,it).toLowerCase();return"*"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=N[e+" "];return t||(t=RegExp("(^|"+P+")"+e+"("+P+"|$)"))&&N(e,function(e){return t.test("string"==typeof e.className&&e.className||typeof e.getAttribute!==j&&e.getAttribute("class")||"")})},ATTR:function(e,t,n){return function(r){var i=at.attr(r,e);return null==i?"!="===t:t?(i+="","="===t?i===n:"!="===t?i!==n:"^="===t?n&&0===i.indexOf(n):"*="===t?n&&i.indexOf(n)>-1:"$="===t?n&&i.slice(-n.length)===n:"~="===t?(" "+i+" ").indexOf(n)>-1:"|="===t?i===n||i.slice(0,n.length+1)===n+"-":!1):!0}},CHILD:function(e,t,n,r,i){var o="nth"!==e.slice(0,3),a="last"!==e.slice(-4),s="of-type"===t;return 1===r&&0===i?function(e){return!!e.parentNode}:function(t,n,l){var u,c,p,f,d,h,g=o!==a?"nextSibling":"previousSibling",m=t.parentNode,y=s&&t.nodeName.toLowerCase(),v=!l&&!s;if(m){if(o){while(g){p=t;while(p=p[g])if(s?p.nodeName.toLowerCase()===y:1===p.nodeType)return!1;h=g="only"===e&&!h&&"nextSibling"}return!0}if(h=[a?m.firstChild:m.lastChild],a&&v){c=m[b]||(m[b]={}),u=c[e]||[],d=u[0]===T&&u[1],f=u[0]===T&&u[2],p=d&&m.childNodes[d];while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if(1===p.nodeType&&++f&&p===t){c[e]=[T,d,f];break}}else if(v&&(u=(t[b]||(t[b]={}))[e])&&u[0]===T)f=u[1];else while(p=++d&&p&&p[g]||(f=d=0)||h.pop())if((s?p.nodeName.toLowerCase()===y:1===p.nodeType)&&++f&&(v&&((p[b]||(p[b]={}))[e]=[T,f]),p===t))break;return f-=i,f===r||0===f%r&&f/r>=0}}},PSEUDO:function(e,t){var n,r=o.pseudos[e]||o.setFilters[e.toLowerCase()]||at.error("unsupported pseudo: "+e);return r[b]?r(t):r.length>1?(n=[e,e,"",t],o.setFilters.hasOwnProperty(e.toLowerCase())?ut(function(e,n){var i,o=r(e,t),a=o.length;while(a--)i=F.call(e,o[a]),e[i]=!(n[i]=o[a])}):function(e){return r(e,0,n)}):r}},pseudos:{not:ut(function(e){var t=[],n=[],r=l(e.replace(z,"$1"));return r[b]?ut(function(e,t,n,i){var o,a=r(e,null,i,[]),s=e.length;while(s--)(o=a[s])&&(e[s]=!(t[s]=o))}):function(e,i,o){return t[0]=e,r(t,null,o,n),!n.pop()}}),has:ut(function(e){return function(t){return at(e,t).length>0}}),contains:ut(function(e){return function(t){return(t.textContent||t.innerText||a(t)).indexOf(e)>-1}}),lang:ut(function(e){return G.test(e||"")||at.error("unsupported lang: "+e),e=e.replace(rt,it).toLowerCase(),function(t){var n;do if(n=h?t.lang:t.getAttribute("xml:lang")||t.getAttribute("lang"))return n=n.toLowerCase(),n===e||0===n.indexOf(e+"-");while((t=t.parentNode)&&1===t.nodeType);return!1}}),target:function(t){var n=e.location&&e.location.hash;return n&&n.slice(1)===t.id},root:function(e){return e===d},focus:function(e){return e===f.activeElement&&(!f.hasFocus||f.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:function(e){return e.disabled===!1},disabled:function(e){return e.disabled===!0},checked:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&!!e.checked||"option"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,e.selected===!0},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeName>"@"||3===e.nodeType||4===e.nodeType)return!1;return!0},parent:function(e){return!o.pseudos.empty(e)},header:function(e){return tt.test(e.nodeName)},input:function(e){return et.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return"input"===t&&"button"===e.type||"button"===t},text:function(e){var t;return"input"===e.nodeName.toLowerCase()&&"text"===e.type&&(null==(t=e.getAttribute("type"))||t.toLowerCase()===e.type)},first:vt(function(){return[0]}),last:vt(function(e,t){return[t-1]}),eq:vt(function(e,t,n){return[0>n?n+t:n]}),even:vt(function(e,t){var n=0;for(;t>n;n+=2)e.push(n);return e}),odd:vt(function(e,t){var n=1;for(;t>n;n+=2)e.push(n);return e}),lt:vt(function(e,t,n){var r=0>n?n+t:n;for(;--r>=0;)e.push(r);return e}),gt:vt(function(e,t,n){var r=0>n?n+t:n;for(;t>++r;)e.push(r);return e})}};for(n in{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})o.pseudos[n]=mt(n);for(n in{submit:!0,reset:!0})o.pseudos[n]=yt(n);function bt(e,t){var n,r,i,a,s,l,u,c=k[e+" "];if(c)return t?0:c.slice(0);s=e,l=[],u=o.preFilter;while(s){(!n||(r=X.exec(s)))&&(r&&(s=s.slice(r[0].length)||s),l.push(i=[])),n=!1,(r=U.exec(s))&&(n=r.shift(),i.push({value:n,type:r[0].replace(z," ")}),s=s.slice(n.length));for(a in o.filter)!(r=Q[a].exec(s))||u[a]&&!(r=u[a](r))||(n=r.shift(),i.push({value:n,type:a,matches:r}),s=s.slice(n.length));if(!n)break}return t?s.length:s?at.error(e):k(e,l).slice(0)}function xt(e){var t=0,n=e.length,r="";for(;n>t;t++)r+=e[t].value;return r}function wt(e,t,n){var r=t.dir,o=n&&"parentNode"===r,a=C++;return t.first?function(t,n,i){while(t=t[r])if(1===t.nodeType||o)return e(t,n,i)}:function(t,n,s){var l,u,c,p=T+" "+a;if(s){while(t=t[r])if((1===t.nodeType||o)&&e(t,n,s))return!0}else while(t=t[r])if(1===t.nodeType||o)if(c=t[b]||(t[b]={}),(u=c[r])&&u[0]===p){if((l=u[1])===!0||l===i)return l===!0}else if(u=c[r]=[p],u[1]=e(t,n,s)||i,u[1]===!0)return!0}}function Tt(e){return e.length>1?function(t,n,r){var i=e.length;while(i--)if(!e[i](t,n,r))return!1;return!0}:e[0]}function Ct(e,t,n,r,i){var o,a=[],s=0,l=e.length,u=null!=t;for(;l>s;s++)(o=e[s])&&(!n||n(o,r,i))&&(a.push(o),u&&t.push(s));return a}function Nt(e,t,n,r,i,o){return r&&!r[b]&&(r=Nt(r)),i&&!i[b]&&(i=Nt(i,o)),ut(function(o,a,s,l){var u,c,p,f=[],d=[],h=a.length,g=o||St(t||"*",s.nodeType?[s]:s,[]),m=!e||!o&&t?g:Ct(g,f,e,s,l),y=n?i||(o?e:h||r)?[]:a:m;if(n&&n(m,y,s,l),r){u=Ct(y,d),r(u,[],s,l),c=u.length;while(c--)(p=u[c])&&(y[d[c]]=!(m[d[c]]=p))}if(o){if(i||e){if(i){u=[],c=y.length;while(c--)(p=y[c])&&u.push(m[c]=p);i(null,y=[],u,l)}c=y.length;while(c--)(p=y[c])&&(u=i?F.call(o,p):f[c])>-1&&(o[u]=!(a[u]=p))}}else y=Ct(y===a?y.splice(h,y.length):y),i?i(null,a,y,l):M.apply(a,y)})}function kt(e){var t,n,r,i=e.length,a=o.relative[e[0].type],s=a||o.relative[" "],l=a?1:0,c=wt(function(e){return e===t},s,!0),p=wt(function(e){return F.call(t,e)>-1},s,!0),f=[function(e,n,r){return!a&&(r||n!==u)||((t=n).nodeType?c(e,n,r):p(e,n,r))}];for(;i>l;l++)if(n=o.relative[e[l].type])f=[wt(Tt(f),n)];else{if(n=o.filter[e[l].type].apply(null,e[l].matches),n[b]){for(r=++l;i>r;r++)if(o.relative[e[r].type])break;return Nt(l>1&&Tt(f),l>1&&xt(e.slice(0,l-1).concat({value:" "===e[l-2].type?"*":""})).replace(z,"$1"),n,r>l&&kt(e.slice(l,r)),i>r&&kt(e=e.slice(r)),i>r&&xt(e))}f.push(n)}return Tt(f)}function Et(e,t){var n=0,r=t.length>0,a=e.length>0,s=function(s,l,c,p,d){var h,g,m,y=[],v=0,b="0",x=s&&[],w=null!=d,C=u,N=s||a&&o.find.TAG("*",d&&l.parentNode||l),k=T+=null==C?1:Math.random()||.1;for(w&&(u=l!==f&&l,i=n);null!=(h=N[b]);b++){if(a&&h){g=0;while(m=e[g++])if(m(h,l,c)){p.push(h);break}w&&(T=k,i=++n)}r&&((h=!m&&h)&&v--,s&&x.push(h))}if(v+=b,r&&b!==v){g=0;while(m=t[g++])m(x,y,l,c);if(s){if(v>0)while(b--)x[b]||y[b]||(y[b]=q.call(p));y=Ct(y)}M.apply(p,y),w&&!s&&y.length>0&&v+t.length>1&&at.uniqueSort(p)}return w&&(T=k,u=C),x};return r?ut(s):s}l=at.compile=function(e,t){var n,r=[],i=[],o=E[e+" "];if(!o){t||(t=bt(e)),n=t.length;while(n--)o=kt(t[n]),o[b]?r.push(o):i.push(o);o=E(e,Et(i,r))}return o};function St(e,t,n){var r=0,i=t.length;for(;i>r;r++)at(e,t[r],n);return n}function At(e,t,n,i){var a,s,u,c,p,f=bt(e);if(!i&&1===f.length){if(s=f[0]=f[0].slice(0),s.length>2&&"ID"===(u=s[0]).type&&r.getById&&9===t.nodeType&&h&&o.relative[s[1].type]){if(t=(o.find.ID(u.matches[0].replace(rt,it),t)||[])[0],!t)return n;e=e.slice(s.shift().value.length)}a=Q.needsContext.test(e)?0:s.length;while(a--){if(u=s[a],o.relative[c=u.type])break;if((p=o.find[c])&&(i=p(u.matches[0].replace(rt,it),V.test(s[0].type)&&t.parentNode||t))){if(s.splice(a,1),e=i.length&&xt(s),!e)return M.apply(n,i),n;break}}}return l(e,f)(i,t,!h,n,V.test(e)),n}o.pseudos.nth=o.pseudos.eq;function jt(){}jt.prototype=o.filters=o.pseudos,o.setFilters=new jt,r.sortStable=b.split("").sort(A).join("")===b,p(),[0,0].sort(A),r.detectDuplicates=S,x.find=at,x.expr=at.selectors,x.expr[":"]=x.expr.pseudos,x.unique=at.uniqueSort,x.text=at.getText,x.isXMLDoc=at.isXML,x.contains=at.contains}(e);var O={};function F(e){var t=O[e]={};return x.each(e.match(T)||[],function(e,n){t[n]=!0}),t}x.Callbacks=function(e){e="string"==typeof e?O[e]||F(e):x.extend({},e);var n,r,i,o,a,s,l=[],u=!e.once&&[],c=function(t){for(r=e.memory&&t,i=!0,a=s||0,s=0,o=l.length,n=!0;l&&o>a;a++)if(l[a].apply(t[0],t[1])===!1&&e.stopOnFalse){r=!1;break}n=!1,l&&(u?u.length&&c(u.shift()):r?l=[]:p.disable())},p={add:function(){if(l){var t=l.length;(function i(t){x.each(t,function(t,n){var r=x.type(n);"function"===r?e.unique&&p.has(n)||l.push(n):n&&n.length&&"string"!==r&&i(n)})})(arguments),n?o=l.length:r&&(s=t,c(r))}return this},remove:function(){return l&&x.each(arguments,function(e,t){var r;while((r=x.inArray(t,l,r))>-1)l.splice(r,1),n&&(o>=r&&o--,a>=r&&a--)}),this},has:function(e){return e?x.inArray(e,l)>-1:!(!l||!l.length)},empty:function(){return l=[],o=0,this},disable:function(){return l=u=r=t,this},disabled:function(){return!l},lock:function(){return u=t,r||p.disable(),this},locked:function(){return!u},fireWith:function(e,t){return t=t||[],t=[e,t.slice?t.slice():t],!l||i&&!u||(n?u.push(t):c(t)),this},fire:function(){return p.fireWith(this,arguments),this},fired:function(){return!!i}};return p},x.extend({Deferred:function(e){var t=[["resolve","done",x.Callbacks("once memory"),"resolved"],["reject","fail",x.Callbacks("once memory"),"rejected"],["notify","progress",x.Callbacks("memory")]],n="pending",r={state:function(){return n},always:function(){return i.done(arguments).fail(arguments),this},then:function(){var e=arguments;return x.Deferred(function(n){x.each(t,function(t,o){var a=o[0],s=x.isFunction(e[t])&&e[t];i[o[1]](function(){var e=s&&s.apply(this,arguments);e&&x.isFunction(e.promise)?e.promise().done(n.resolve).fail(n.reject).progress(n.notify):n[a+"With"](this===r?n.promise():this,s?[e]:arguments)})}),e=null}).promise()},promise:function(e){return null!=e?x.extend(e,r):r}},i={};return r.pipe=r.then,x.each(t,function(e,o){var a=o[2],s=o[3];r[o[1]]=a.add,s&&a.add(function(){n=s},t[1^e][2].disable,t[2][2].lock),i[o[0]]=function(){return i[o[0]+"With"](this===i?r:this,arguments),this},i[o[0]+"With"]=a.fireWith}),r.promise(i),e&&e.call(i,i),i},when:function(e){var t=0,n=g.call(arguments),r=n.length,i=1!==r||e&&x.isFunction(e.promise)?r:0,o=1===i?e:x.Deferred(),a=function(e,t,n){return function(r){t[e]=this,n[e]=arguments.length>1?g.call(arguments):r,n===s?o.notifyWith(t,n):--i||o.resolveWith(t,n)}},s,l,u;if(r>1)for(s=Array(r),l=Array(r),u=Array(r);r>t;t++)n[t]&&x.isFunction(n[t].promise)?n[t].promise().done(a(t,u,n)).fail(o.reject).progress(a(t,l,s)):--i;return i||o.resolveWith(u,n),o.promise()}}),x.support=function(t){var n,r,o,s,l,u,c,p,f,d=a.createElement("div");if(d.setAttribute("className","t"),d.innerHTML="
a",n=d.getElementsByTagName("*")||[],r=d.getElementsByTagName("a")[0],!r||!r.style||!n.length)return t;s=a.createElement("select"),u=s.appendChild(a.createElement("option")),o=d.getElementsByTagName("input")[0],r.style.cssText="top:1px;float:left;opacity:.5",t.getSetAttribute="t"!==d.className,t.leadingWhitespace=3===d.firstChild.nodeType,t.tbody=!d.getElementsByTagName("tbody").length,t.htmlSerialize=!!d.getElementsByTagName("link").length,t.style=/top/.test(r.getAttribute("style")),t.hrefNormalized="/a"===r.getAttribute("href"),t.opacity=/^0.5/.test(r.style.opacity),t.cssFloat=!!r.style.cssFloat,t.checkOn=!!o.value,t.optSelected=u.selected,t.enctype=!!a.createElement("form").enctype,t.html5Clone="<:nav>"!==a.createElement("nav").cloneNode(!0).outerHTML,t.inlineBlockNeedsLayout=!1,t.shrinkWrapBlocks=!1,t.pixelPosition=!1,t.deleteExpando=!0,t.noCloneEvent=!0,t.reliableMarginRight=!0,t.boxSizingReliable=!0,o.checked=!0,t.noCloneChecked=o.cloneNode(!0).checked,s.disabled=!0,t.optDisabled=!u.disabled;try{delete d.test}catch(h){t.deleteExpando=!1}o=a.createElement("input"),o.setAttribute("value",""),t.input=""===o.getAttribute("value"),o.value="t",o.setAttribute("type","radio"),t.radioValue="t"===o.value,o.setAttribute("checked","t"),o.setAttribute("name","t"),l=a.createDocumentFragment(),l.appendChild(o),t.appendChecked=o.checked,t.checkClone=l.cloneNode(!0).cloneNode(!0).lastChild.checked,d.attachEvent&&(d.attachEvent("onclick",function(){t.noCloneEvent=!1}),d.cloneNode(!0).click());for(f in{submit:!0,change:!0,focusin:!0})d.setAttribute(c="on"+f,"t"),t[f+"Bubbles"]=c in e||d.attributes[c].expando===!1;d.style.backgroundClip="content-box",d.cloneNode(!0).style.backgroundClip="",t.clearCloneStyle="content-box"===d.style.backgroundClip;for(f in x(t))break;return t.ownLast="0"!==f,x(function(){var n,r,o,s="padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",l=a.getElementsByTagName("body")[0];l&&(n=a.createElement("div"),n.style.cssText="border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px",l.appendChild(n).appendChild(d),d.innerHTML="
t
",o=d.getElementsByTagName("td"),o[0].style.cssText="padding:0;margin:0;border:0;display:none",p=0===o[0].offsetHeight,o[0].style.display="",o[1].style.display="none",t.reliableHiddenOffsets=p&&0===o[0].offsetHeight,d.innerHTML="",d.style.cssText="box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;",x.swap(l,null!=l.style.zoom?{zoom:1}:{},function(){t.boxSizing=4===d.offsetWidth}),e.getComputedStyle&&(t.pixelPosition="1%"!==(e.getComputedStyle(d,null)||{}).top,t.boxSizingReliable="4px"===(e.getComputedStyle(d,null)||{width:"4px"}).width,r=d.appendChild(a.createElement("div")),r.style.cssText=d.style.cssText=s,r.style.marginRight=r.style.width="0",d.style.width="1px",t.reliableMarginRight=!parseFloat((e.getComputedStyle(r,null)||{}).marginRight)),typeof d.style.zoom!==i&&(d.innerHTML="",d.style.cssText=s+"width:1px;padding:1px;display:inline;zoom:1",t.inlineBlockNeedsLayout=3===d.offsetWidth,d.style.display="block",d.innerHTML="
",d.firstChild.style.width="5px",t.shrinkWrapBlocks=3!==d.offsetWidth,t.inlineBlockNeedsLayout&&(l.style.zoom=1)),l.removeChild(n),n=d=o=r=null) +}),n=s=l=u=r=o=null,t}({});var B=/(?:\{[\s\S]*\}|\[[\s\S]*\])$/,P=/([A-Z])/g;function R(e,n,r,i){if(x.acceptData(e)){var o,a,s=x.expando,l=e.nodeType,u=l?x.cache:e,c=l?e[s]:e[s]&&s;if(c&&u[c]&&(i||u[c].data)||r!==t||"string"!=typeof n)return c||(c=l?e[s]=p.pop()||x.guid++:s),u[c]||(u[c]=l?{}:{toJSON:x.noop}),("object"==typeof n||"function"==typeof n)&&(i?u[c]=x.extend(u[c],n):u[c].data=x.extend(u[c].data,n)),a=u[c],i||(a.data||(a.data={}),a=a.data),r!==t&&(a[x.camelCase(n)]=r),"string"==typeof n?(o=a[n],null==o&&(o=a[x.camelCase(n)])):o=a,o}}function W(e,t,n){if(x.acceptData(e)){var r,i,o=e.nodeType,a=o?x.cache:e,s=o?e[x.expando]:x.expando;if(a[s]){if(t&&(r=n?a[s]:a[s].data)){x.isArray(t)?t=t.concat(x.map(t,x.camelCase)):t in r?t=[t]:(t=x.camelCase(t),t=t in r?[t]:t.split(" ")),i=t.length;while(i--)delete r[t[i]];if(n?!I(r):!x.isEmptyObject(r))return}(n||(delete a[s].data,I(a[s])))&&(o?x.cleanData([e],!0):x.support.deleteExpando||a!=a.window?delete a[s]:a[s]=null)}}}x.extend({cache:{},noData:{applet:!0,embed:!0,object:"clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"},hasData:function(e){return e=e.nodeType?x.cache[e[x.expando]]:e[x.expando],!!e&&!I(e)},data:function(e,t,n){return R(e,t,n)},removeData:function(e,t){return W(e,t)},_data:function(e,t,n){return R(e,t,n,!0)},_removeData:function(e,t){return W(e,t,!0)},acceptData:function(e){if(e.nodeType&&1!==e.nodeType&&9!==e.nodeType)return!1;var t=e.nodeName&&x.noData[e.nodeName.toLowerCase()];return!t||t!==!0&&e.getAttribute("classid")===t}}),x.fn.extend({data:function(e,n){var r,i,o=null,a=0,s=this[0];if(e===t){if(this.length&&(o=x.data(s),1===s.nodeType&&!x._data(s,"parsedAttrs"))){for(r=s.attributes;r.length>a;a++)i=r[a].name,0===i.indexOf("data-")&&(i=x.camelCase(i.slice(5)),$(s,i,o[i]));x._data(s,"parsedAttrs",!0)}return o}return"object"==typeof e?this.each(function(){x.data(this,e)}):arguments.length>1?this.each(function(){x.data(this,e,n)}):s?$(s,e,x.data(s,e)):null},removeData:function(e){return this.each(function(){x.removeData(this,e)})}});function $(e,n,r){if(r===t&&1===e.nodeType){var i="data-"+n.replace(P,"-$1").toLowerCase();if(r=e.getAttribute(i),"string"==typeof r){try{r="true"===r?!0:"false"===r?!1:"null"===r?null:+r+""===r?+r:B.test(r)?x.parseJSON(r):r}catch(o){}x.data(e,n,r)}else r=t}return r}function I(e){var t;for(t in e)if(("data"!==t||!x.isEmptyObject(e[t]))&&"toJSON"!==t)return!1;return!0}x.extend({queue:function(e,n,r){var i;return e?(n=(n||"fx")+"queue",i=x._data(e,n),r&&(!i||x.isArray(r)?i=x._data(e,n,x.makeArray(r)):i.push(r)),i||[]):t},dequeue:function(e,t){t=t||"fx";var n=x.queue(e,t),r=n.length,i=n.shift(),o=x._queueHooks(e,t),a=function(){x.dequeue(e,t)};"inprogress"===i&&(i=n.shift(),r--),i&&("fx"===t&&n.unshift("inprogress"),delete o.stop,i.call(e,a,o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+"queueHooks";return x._data(e,n)||x._data(e,n,{empty:x.Callbacks("once memory").add(function(){x._removeData(e,t+"queue"),x._removeData(e,n)})})}}),x.fn.extend({queue:function(e,n){var r=2;return"string"!=typeof e&&(n=e,e="fx",r--),r>arguments.length?x.queue(this[0],e):n===t?this:this.each(function(){var t=x.queue(this,e,n);x._queueHooks(this,e),"fx"===e&&"inprogress"!==t[0]&&x.dequeue(this,e)})},dequeue:function(e){return this.each(function(){x.dequeue(this,e)})},delay:function(e,t){return e=x.fx?x.fx.speeds[e]||e:e,t=t||"fx",this.queue(t,function(t,n){var r=setTimeout(t,e);n.stop=function(){clearTimeout(r)}})},clearQueue:function(e){return this.queue(e||"fx",[])},promise:function(e,n){var r,i=1,o=x.Deferred(),a=this,s=this.length,l=function(){--i||o.resolveWith(a,[a])};"string"!=typeof e&&(n=e,e=t),e=e||"fx";while(s--)r=x._data(a[s],e+"queueHooks"),r&&r.empty&&(i++,r.empty.add(l));return l(),o.promise(n)}});var z,X,U=/[\t\r\n\f]/g,V=/\r/g,Y=/^(?:input|select|textarea|button|object)$/i,J=/^(?:a|area)$/i,G=/^(?:checked|selected)$/i,Q=x.support.getSetAttribute,K=x.support.input;x.fn.extend({attr:function(e,t){return x.access(this,x.attr,e,t,arguments.length>1)},removeAttr:function(e){return this.each(function(){x.removeAttr(this,e)})},prop:function(e,t){return x.access(this,x.prop,e,t,arguments.length>1)},removeProp:function(e){return e=x.propFix[e]||e,this.each(function(){try{this[e]=t,delete this[e]}catch(n){}})},addClass:function(e){var t,n,r,i,o,a=0,s=this.length,l="string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).addClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):" ")){o=0;while(i=t[o++])0>r.indexOf(" "+i+" ")&&(r+=i+" ");n.className=x.trim(r)}return this},removeClass:function(e){var t,n,r,i,o,a=0,s=this.length,l=0===arguments.length||"string"==typeof e&&e;if(x.isFunction(e))return this.each(function(t){x(this).removeClass(e.call(this,t,this.className))});if(l)for(t=(e||"").match(T)||[];s>a;a++)if(n=this[a],r=1===n.nodeType&&(n.className?(" "+n.className+" ").replace(U," "):"")){o=0;while(i=t[o++])while(r.indexOf(" "+i+" ")>=0)r=r.replace(" "+i+" "," ");n.className=e?x.trim(r):""}return this},toggleClass:function(e,t){var n=typeof e,r="boolean"==typeof t;return x.isFunction(e)?this.each(function(n){x(this).toggleClass(e.call(this,n,this.className,t),t)}):this.each(function(){if("string"===n){var o,a=0,s=x(this),l=t,u=e.match(T)||[];while(o=u[a++])l=r?l:!s.hasClass(o),s[l?"addClass":"removeClass"](o)}else(n===i||"boolean"===n)&&(this.className&&x._data(this,"__className__",this.className),this.className=this.className||e===!1?"":x._data(this,"__className__")||"")})},hasClass:function(e){var t=" "+e+" ",n=0,r=this.length;for(;r>n;n++)if(1===this[n].nodeType&&(" "+this[n].className+" ").replace(U," ").indexOf(t)>=0)return!0;return!1},val:function(e){var n,r,i,o=this[0];{if(arguments.length)return i=x.isFunction(e),this.each(function(n){var o;1===this.nodeType&&(o=i?e.call(this,n,x(this).val()):e,null==o?o="":"number"==typeof o?o+="":x.isArray(o)&&(o=x.map(o,function(e){return null==e?"":e+""})),r=x.valHooks[this.type]||x.valHooks[this.nodeName.toLowerCase()],r&&"set"in r&&r.set(this,o,"value")!==t||(this.value=o))});if(o)return r=x.valHooks[o.type]||x.valHooks[o.nodeName.toLowerCase()],r&&"get"in r&&(n=r.get(o,"value"))!==t?n:(n=o.value,"string"==typeof n?n.replace(V,""):null==n?"":n)}}}),x.extend({valHooks:{option:{get:function(e){var t=x.find.attr(e,"value");return null!=t?t:e.text}},select:{get:function(e){var t,n,r=e.options,i=e.selectedIndex,o="select-one"===e.type||0>i,a=o?null:[],s=o?i+1:r.length,l=0>i?s:o?i:0;for(;s>l;l++)if(n=r[l],!(!n.selected&&l!==i||(x.support.optDisabled?n.disabled:null!==n.getAttribute("disabled"))||n.parentNode.disabled&&x.nodeName(n.parentNode,"optgroup"))){if(t=x(n).val(),o)return t;a.push(t)}return a},set:function(e,t){var n,r,i=e.options,o=x.makeArray(t),a=i.length;while(a--)r=i[a],(r.selected=x.inArray(x(r).val(),o)>=0)&&(n=!0);return n||(e.selectedIndex=-1),o}}},attr:function(e,n,r){var o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return typeof e.getAttribute===i?x.prop(e,n,r):(1===s&&x.isXMLDoc(e)||(n=n.toLowerCase(),o=x.attrHooks[n]||(x.expr.match.bool.test(n)?X:z)),r===t?o&&"get"in o&&null!==(a=o.get(e,n))?a:(a=x.find.attr(e,n),null==a?t:a):null!==r?o&&"set"in o&&(a=o.set(e,r,n))!==t?a:(e.setAttribute(n,r+""),r):(x.removeAttr(e,n),t))},removeAttr:function(e,t){var n,r,i=0,o=t&&t.match(T);if(o&&1===e.nodeType)while(n=o[i++])r=x.propFix[n]||n,x.expr.match.bool.test(n)?K&&Q||!G.test(n)?e[r]=!1:e[x.camelCase("default-"+n)]=e[r]=!1:x.attr(e,n,""),e.removeAttribute(Q?n:r)},attrHooks:{type:{set:function(e,t){if(!x.support.radioValue&&"radio"===t&&x.nodeName(e,"input")){var n=e.value;return e.setAttribute("type",t),n&&(e.value=n),t}}}},propFix:{"for":"htmlFor","class":"className"},prop:function(e,n,r){var i,o,a,s=e.nodeType;if(e&&3!==s&&8!==s&&2!==s)return a=1!==s||!x.isXMLDoc(e),a&&(n=x.propFix[n]||n,o=x.propHooks[n]),r!==t?o&&"set"in o&&(i=o.set(e,r,n))!==t?i:e[n]=r:o&&"get"in o&&null!==(i=o.get(e,n))?i:e[n]},propHooks:{tabIndex:{get:function(e){var t=x.find.attr(e,"tabindex");return t?parseInt(t,10):Y.test(e.nodeName)||J.test(e.nodeName)&&e.href?0:-1}}}}),X={set:function(e,t,n){return t===!1?x.removeAttr(e,n):K&&Q||!G.test(n)?e.setAttribute(!Q&&x.propFix[n]||n,n):e[x.camelCase("default-"+n)]=e[n]=!0,n}},x.each(x.expr.match.bool.source.match(/\w+/g),function(e,n){var r=x.expr.attrHandle[n]||x.find.attr;x.expr.attrHandle[n]=K&&Q||!G.test(n)?function(e,n,i){var o=x.expr.attrHandle[n],a=i?t:(x.expr.attrHandle[n]=t)!=r(e,n,i)?n.toLowerCase():null;return x.expr.attrHandle[n]=o,a}:function(e,n,r){return r?t:e[x.camelCase("default-"+n)]?n.toLowerCase():null}}),K&&Q||(x.attrHooks.value={set:function(e,n,r){return x.nodeName(e,"input")?(e.defaultValue=n,t):z&&z.set(e,n,r)}}),Q||(z={set:function(e,n,r){var i=e.getAttributeNode(r);return i||e.setAttributeNode(i=e.ownerDocument.createAttribute(r)),i.value=n+="","value"===r||n===e.getAttribute(r)?n:t}},x.expr.attrHandle.id=x.expr.attrHandle.name=x.expr.attrHandle.coords=function(e,n,r){var i;return r?t:(i=e.getAttributeNode(n))&&""!==i.value?i.value:null},x.valHooks.button={get:function(e,n){var r=e.getAttributeNode(n);return r&&r.specified?r.value:t},set:z.set},x.attrHooks.contenteditable={set:function(e,t,n){z.set(e,""===t?!1:t,n)}},x.each(["width","height"],function(e,n){x.attrHooks[n]={set:function(e,r){return""===r?(e.setAttribute(n,"auto"),r):t}}})),x.support.hrefNormalized||x.each(["href","src"],function(e,t){x.propHooks[t]={get:function(e){return e.getAttribute(t,4)}}}),x.support.style||(x.attrHooks.style={get:function(e){return e.style.cssText||t},set:function(e,t){return e.style.cssText=t+""}}),x.support.optSelected||(x.propHooks.selected={get:function(e){var t=e.parentNode;return t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex),null}}),x.each(["tabIndex","readOnly","maxLength","cellSpacing","cellPadding","rowSpan","colSpan","useMap","frameBorder","contentEditable"],function(){x.propFix[this.toLowerCase()]=this}),x.support.enctype||(x.propFix.enctype="encoding"),x.each(["radio","checkbox"],function(){x.valHooks[this]={set:function(e,n){return x.isArray(n)?e.checked=x.inArray(x(e).val(),n)>=0:t}},x.support.checkOn||(x.valHooks[this].get=function(e){return null===e.getAttribute("value")?"on":e.value})});var Z=/^(?:input|select|textarea)$/i,et=/^key/,tt=/^(?:mouse|contextmenu)|click/,nt=/^(?:focusinfocus|focusoutblur)$/,rt=/^([^.]*)(?:\.(.+)|)$/;function it(){return!0}function ot(){return!1}function at(){try{return a.activeElement}catch(e){}}x.event={global:{},add:function(e,n,r,o,a){var s,l,u,c,p,f,d,h,g,m,y,v=x._data(e);if(v){r.handler&&(c=r,r=c.handler,a=c.selector),r.guid||(r.guid=x.guid++),(l=v.events)||(l=v.events={}),(f=v.handle)||(f=v.handle=function(e){return typeof x===i||e&&x.event.triggered===e.type?t:x.event.dispatch.apply(f.elem,arguments)},f.elem=e),n=(n||"").match(T)||[""],u=n.length;while(u--)s=rt.exec(n[u])||[],g=y=s[1],m=(s[2]||"").split(".").sort(),g&&(p=x.event.special[g]||{},g=(a?p.delegateType:p.bindType)||g,p=x.event.special[g]||{},d=x.extend({type:g,origType:y,data:o,handler:r,guid:r.guid,selector:a,needsContext:a&&x.expr.match.needsContext.test(a),namespace:m.join(".")},c),(h=l[g])||(h=l[g]=[],h.delegateCount=0,p.setup&&p.setup.call(e,o,m,f)!==!1||(e.addEventListener?e.addEventListener(g,f,!1):e.attachEvent&&e.attachEvent("on"+g,f))),p.add&&(p.add.call(e,d),d.handler.guid||(d.handler.guid=r.guid)),a?h.splice(h.delegateCount++,0,d):h.push(d),x.event.global[g]=!0);e=null}},remove:function(e,t,n,r,i){var o,a,s,l,u,c,p,f,d,h,g,m=x.hasData(e)&&x._data(e);if(m&&(c=m.events)){t=(t||"").match(T)||[""],u=t.length;while(u--)if(s=rt.exec(t[u])||[],d=g=s[1],h=(s[2]||"").split(".").sort(),d){p=x.event.special[d]||{},d=(r?p.delegateType:p.bindType)||d,f=c[d]||[],s=s[2]&&RegExp("(^|\\.)"+h.join("\\.(?:.*\\.|)")+"(\\.|$)"),l=o=f.length;while(o--)a=f[o],!i&&g!==a.origType||n&&n.guid!==a.guid||s&&!s.test(a.namespace)||r&&r!==a.selector&&("**"!==r||!a.selector)||(f.splice(o,1),a.selector&&f.delegateCount--,p.remove&&p.remove.call(e,a));l&&!f.length&&(p.teardown&&p.teardown.call(e,h,m.handle)!==!1||x.removeEvent(e,d,m.handle),delete c[d])}else for(d in c)x.event.remove(e,d+t[u],n,r,!0);x.isEmptyObject(c)&&(delete m.handle,x._removeData(e,"events"))}},trigger:function(n,r,i,o){var s,l,u,c,p,f,d,h=[i||a],g=v.call(n,"type")?n.type:n,m=v.call(n,"namespace")?n.namespace.split("."):[];if(u=f=i=i||a,3!==i.nodeType&&8!==i.nodeType&&!nt.test(g+x.event.triggered)&&(g.indexOf(".")>=0&&(m=g.split("."),g=m.shift(),m.sort()),l=0>g.indexOf(":")&&"on"+g,n=n[x.expando]?n:new x.Event(g,"object"==typeof n&&n),n.isTrigger=o?2:3,n.namespace=m.join("."),n.namespace_re=n.namespace?RegExp("(^|\\.)"+m.join("\\.(?:.*\\.|)")+"(\\.|$)"):null,n.result=t,n.target||(n.target=i),r=null==r?[n]:x.makeArray(r,[n]),p=x.event.special[g]||{},o||!p.trigger||p.trigger.apply(i,r)!==!1)){if(!o&&!p.noBubble&&!x.isWindow(i)){for(c=p.delegateType||g,nt.test(c+g)||(u=u.parentNode);u;u=u.parentNode)h.push(u),f=u;f===(i.ownerDocument||a)&&h.push(f.defaultView||f.parentWindow||e)}d=0;while((u=h[d++])&&!n.isPropagationStopped())n.type=d>1?c:p.bindType||g,s=(x._data(u,"events")||{})[n.type]&&x._data(u,"handle"),s&&s.apply(u,r),s=l&&u[l],s&&x.acceptData(u)&&s.apply&&s.apply(u,r)===!1&&n.preventDefault();if(n.type=g,!o&&!n.isDefaultPrevented()&&(!p._default||p._default.apply(h.pop(),r)===!1)&&x.acceptData(i)&&l&&i[g]&&!x.isWindow(i)){f=i[l],f&&(i[l]=null),x.event.triggered=g;try{i[g]()}catch(y){}x.event.triggered=t,f&&(i[l]=f)}return n.result}},dispatch:function(e){e=x.event.fix(e);var n,r,i,o,a,s=[],l=g.call(arguments),u=(x._data(this,"events")||{})[e.type]||[],c=x.event.special[e.type]||{};if(l[0]=e,e.delegateTarget=this,!c.preDispatch||c.preDispatch.call(this,e)!==!1){s=x.event.handlers.call(this,e,u),n=0;while((o=s[n++])&&!e.isPropagationStopped()){e.currentTarget=o.elem,a=0;while((i=o.handlers[a++])&&!e.isImmediatePropagationStopped())(!e.namespace_re||e.namespace_re.test(i.namespace))&&(e.handleObj=i,e.data=i.data,r=((x.event.special[i.origType]||{}).handle||i.handler).apply(o.elem,l),r!==t&&(e.result=r)===!1&&(e.preventDefault(),e.stopPropagation()))}return c.postDispatch&&c.postDispatch.call(this,e),e.result}},handlers:function(e,n){var r,i,o,a,s=[],l=n.delegateCount,u=e.target;if(l&&u.nodeType&&(!e.button||"click"!==e.type))for(;u!=this;u=u.parentNode||this)if(1===u.nodeType&&(u.disabled!==!0||"click"!==e.type)){for(o=[],a=0;l>a;a++)i=n[a],r=i.selector+" ",o[r]===t&&(o[r]=i.needsContext?x(r,this).index(u)>=0:x.find(r,this,null,[u]).length),o[r]&&o.push(i);o.length&&s.push({elem:u,handlers:o})}return n.length>l&&s.push({elem:this,handlers:n.slice(l)}),s},fix:function(e){if(e[x.expando])return e;var t,n,r,i=e.type,o=e,s=this.fixHooks[i];s||(this.fixHooks[i]=s=tt.test(i)?this.mouseHooks:et.test(i)?this.keyHooks:{}),r=s.props?this.props.concat(s.props):this.props,e=new x.Event(o),t=r.length;while(t--)n=r[t],e[n]=o[n];return e.target||(e.target=o.srcElement||a),3===e.target.nodeType&&(e.target=e.target.parentNode),e.metaKey=!!e.metaKey,s.filter?s.filter(e,o):e},props:"altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),fixHooks:{},keyHooks:{props:"char charCode key keyCode".split(" "),filter:function(e,t){return null==e.which&&(e.which=null!=t.charCode?t.charCode:t.keyCode),e}},mouseHooks:{props:"button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),filter:function(e,n){var r,i,o,s=n.button,l=n.fromElement;return null==e.pageX&&null!=n.clientX&&(i=e.target.ownerDocument||a,o=i.documentElement,r=i.body,e.pageX=n.clientX+(o&&o.scrollLeft||r&&r.scrollLeft||0)-(o&&o.clientLeft||r&&r.clientLeft||0),e.pageY=n.clientY+(o&&o.scrollTop||r&&r.scrollTop||0)-(o&&o.clientTop||r&&r.clientTop||0)),!e.relatedTarget&&l&&(e.relatedTarget=l===e.target?n.toElement:l),e.which||s===t||(e.which=1&s?1:2&s?3:4&s?2:0),e}},special:{load:{noBubble:!0},focus:{trigger:function(){if(this!==at()&&this.focus)try{return this.focus(),!1}catch(e){}},delegateType:"focusin"},blur:{trigger:function(){return this===at()&&this.blur?(this.blur(),!1):t},delegateType:"focusout"},click:{trigger:function(){return x.nodeName(this,"input")&&"checkbox"===this.type&&this.click?(this.click(),!1):t},_default:function(e){return x.nodeName(e.target,"a")}},beforeunload:{postDispatch:function(e){e.result!==t&&(e.originalEvent.returnValue=e.result)}}},simulate:function(e,t,n,r){var i=x.extend(new x.Event,n,{type:e,isSimulated:!0,originalEvent:{}});r?x.event.trigger(i,null,t):x.event.dispatch.call(t,i),i.isDefaultPrevented()&&n.preventDefault()}},x.removeEvent=a.removeEventListener?function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n,!1)}:function(e,t,n){var r="on"+t;e.detachEvent&&(typeof e[r]===i&&(e[r]=null),e.detachEvent(r,n))},x.Event=function(e,n){return this instanceof x.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||e.returnValue===!1||e.getPreventDefault&&e.getPreventDefault()?it:ot):this.type=e,n&&x.extend(this,n),this.timeStamp=e&&e.timeStamp||x.now(),this[x.expando]=!0,t):new x.Event(e,n)},x.Event.prototype={isDefaultPrevented:ot,isPropagationStopped:ot,isImmediatePropagationStopped:ot,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=it,e&&(e.preventDefault?e.preventDefault():e.returnValue=!1)},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=it,e&&(e.stopPropagation&&e.stopPropagation(),e.cancelBubble=!0)},stopImmediatePropagation:function(){this.isImmediatePropagationStopped=it,this.stopPropagation()}},x.each({mouseenter:"mouseover",mouseleave:"mouseout"},function(e,t){x.event.special[e]={delegateType:t,bindType:t,handle:function(e){var n,r=this,i=e.relatedTarget,o=e.handleObj;return(!i||i!==r&&!x.contains(r,i))&&(e.type=o.origType,n=o.handler.apply(this,arguments),e.type=t),n}}}),x.support.submitBubbles||(x.event.special.submit={setup:function(){return x.nodeName(this,"form")?!1:(x.event.add(this,"click._submit keypress._submit",function(e){var n=e.target,r=x.nodeName(n,"input")||x.nodeName(n,"button")?n.form:t;r&&!x._data(r,"submitBubbles")&&(x.event.add(r,"submit._submit",function(e){e._submit_bubble=!0}),x._data(r,"submitBubbles",!0))}),t)},postDispatch:function(e){e._submit_bubble&&(delete e._submit_bubble,this.parentNode&&!e.isTrigger&&x.event.simulate("submit",this.parentNode,e,!0))},teardown:function(){return x.nodeName(this,"form")?!1:(x.event.remove(this,"._submit"),t)}}),x.support.changeBubbles||(x.event.special.change={setup:function(){return Z.test(this.nodeName)?(("checkbox"===this.type||"radio"===this.type)&&(x.event.add(this,"propertychange._change",function(e){"checked"===e.originalEvent.propertyName&&(this._just_changed=!0)}),x.event.add(this,"click._change",function(e){this._just_changed&&!e.isTrigger&&(this._just_changed=!1),x.event.simulate("change",this,e,!0)})),!1):(x.event.add(this,"beforeactivate._change",function(e){var t=e.target;Z.test(t.nodeName)&&!x._data(t,"changeBubbles")&&(x.event.add(t,"change._change",function(e){!this.parentNode||e.isSimulated||e.isTrigger||x.event.simulate("change",this.parentNode,e,!0)}),x._data(t,"changeBubbles",!0))}),t)},handle:function(e){var n=e.target;return this!==n||e.isSimulated||e.isTrigger||"radio"!==n.type&&"checkbox"!==n.type?e.handleObj.handler.apply(this,arguments):t},teardown:function(){return x.event.remove(this,"._change"),!Z.test(this.nodeName)}}),x.support.focusinBubbles||x.each({focus:"focusin",blur:"focusout"},function(e,t){var n=0,r=function(e){x.event.simulate(t,e.target,x.event.fix(e),!0)};x.event.special[t]={setup:function(){0===n++&&a.addEventListener(e,r,!0)},teardown:function(){0===--n&&a.removeEventListener(e,r,!0)}}}),x.fn.extend({on:function(e,n,r,i,o){var a,s;if("object"==typeof e){"string"!=typeof n&&(r=r||n,n=t);for(a in e)this.on(a,n,r,e[a],o);return this}if(null==r&&null==i?(i=n,r=n=t):null==i&&("string"==typeof n?(i=r,r=t):(i=r,r=n,n=t)),i===!1)i=ot;else if(!i)return this;return 1===o&&(s=i,i=function(e){return x().off(e),s.apply(this,arguments)},i.guid=s.guid||(s.guid=x.guid++)),this.each(function(){x.event.add(this,e,i,r,n)})},one:function(e,t,n,r){return this.on(e,t,n,r,1)},off:function(e,n,r){var i,o;if(e&&e.preventDefault&&e.handleObj)return i=e.handleObj,x(e.delegateTarget).off(i.namespace?i.origType+"."+i.namespace:i.origType,i.selector,i.handler),this;if("object"==typeof e){for(o in e)this.off(o,n,e[o]);return this}return(n===!1||"function"==typeof n)&&(r=n,n=t),r===!1&&(r=ot),this.each(function(){x.event.remove(this,e,r,n)})},trigger:function(e,t){return this.each(function(){x.event.trigger(e,t,this)})},triggerHandler:function(e,n){var r=this[0];return r?x.event.trigger(e,n,r,!0):t}});var st=/^.[^:#\[\.,]*$/,lt=/^(?:parents|prev(?:Until|All))/,ut=x.expr.match.needsContext,ct={children:!0,contents:!0,next:!0,prev:!0};x.fn.extend({find:function(e){var t,n=[],r=this,i=r.length;if("string"!=typeof e)return this.pushStack(x(e).filter(function(){for(t=0;i>t;t++)if(x.contains(r[t],this))return!0}));for(t=0;i>t;t++)x.find(e,r[t],n);return n=this.pushStack(i>1?x.unique(n):n),n.selector=this.selector?this.selector+" "+e:e,n},has:function(e){var t,n=x(e,this),r=n.length;return this.filter(function(){for(t=0;r>t;t++)if(x.contains(this,n[t]))return!0})},not:function(e){return this.pushStack(ft(this,e||[],!0))},filter:function(e){return this.pushStack(ft(this,e||[],!1))},is:function(e){return!!ft(this,"string"==typeof e&&ut.test(e)?x(e):e||[],!1).length},closest:function(e,t){var n,r=0,i=this.length,o=[],a=ut.test(e)||"string"!=typeof e?x(e,t||this.context):0;for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(11>n.nodeType&&(a?a.index(n)>-1:1===n.nodeType&&x.find.matchesSelector(n,e))){n=o.push(n);break}return this.pushStack(o.length>1?x.unique(o):o)},index:function(e){return e?"string"==typeof e?x.inArray(this[0],x(e)):x.inArray(e.jquery?e[0]:e,this):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){var n="string"==typeof e?x(e,t):x.makeArray(e&&e.nodeType?[e]:e),r=x.merge(this.get(),n);return this.pushStack(x.unique(r))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}});function pt(e,t){do e=e[t];while(e&&1!==e.nodeType);return e}x.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return x.dir(e,"parentNode")},parentsUntil:function(e,t,n){return x.dir(e,"parentNode",n)},next:function(e){return pt(e,"nextSibling")},prev:function(e){return pt(e,"previousSibling")},nextAll:function(e){return x.dir(e,"nextSibling")},prevAll:function(e){return x.dir(e,"previousSibling")},nextUntil:function(e,t,n){return x.dir(e,"nextSibling",n)},prevUntil:function(e,t,n){return x.dir(e,"previousSibling",n)},siblings:function(e){return x.sibling((e.parentNode||{}).firstChild,e)},children:function(e){return x.sibling(e.firstChild)},contents:function(e){return x.nodeName(e,"iframe")?e.contentDocument||e.contentWindow.document:x.merge([],e.childNodes)}},function(e,t){x.fn[e]=function(n,r){var i=x.map(this,t,n);return"Until"!==e.slice(-5)&&(r=n),r&&"string"==typeof r&&(i=x.filter(r,i)),this.length>1&&(ct[e]||(i=x.unique(i)),lt.test(e)&&(i=i.reverse())),this.pushStack(i)}}),x.extend({filter:function(e,t,n){var r=t[0];return n&&(e=":not("+e+")"),1===t.length&&1===r.nodeType?x.find.matchesSelector(r,e)?[r]:[]:x.find.matches(e,x.grep(t,function(e){return 1===e.nodeType}))},dir:function(e,n,r){var i=[],o=e[n];while(o&&9!==o.nodeType&&(r===t||1!==o.nodeType||!x(o).is(r)))1===o.nodeType&&i.push(o),o=o[n];return i},sibling:function(e,t){var n=[];for(;e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n}});function ft(e,t,n){if(x.isFunction(t))return x.grep(e,function(e,r){return!!t.call(e,r,e)!==n});if(t.nodeType)return x.grep(e,function(e){return e===t!==n});if("string"==typeof t){if(st.test(t))return x.filter(t,e,n);t=x.filter(t,e)}return x.grep(e,function(e){return x.inArray(e,t)>=0!==n})}function dt(e){var t=ht.split("|"),n=e.createDocumentFragment();if(n.createElement)while(t.length)n.createElement(t.pop());return n}var ht="abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",gt=/ jQuery\d+="(?:null|\d+)"/g,mt=RegExp("<(?:"+ht+")[\\s/>]","i"),yt=/^\s+/,vt=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,bt=/<([\w:]+)/,xt=/\s*$/g,At={option:[1,""],legend:[1,"
","
"],area:[1,"",""],param:[1,"",""],thead:[1,"","
"],tr:[2,"","
"],col:[2,"","
"],td:[3,"","
"],_default:x.support.htmlSerialize?[0,"",""]:[1,"X
","
"]},jt=dt(a),Dt=jt.appendChild(a.createElement("div"));At.optgroup=At.option,At.tbody=At.tfoot=At.colgroup=At.caption=At.thead,At.th=At.td,x.fn.extend({text:function(e){return x.access(this,function(e){return e===t?x.text(this):this.empty().append((this[0]&&this[0].ownerDocument||a).createTextNode(e))},null,e,arguments.length)},append:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.appendChild(e)}})},prepend:function(){return this.domManip(arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Lt(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return this.domManip(arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},remove:function(e,t){var n,r=e?x.filter(e,this):this,i=0;for(;null!=(n=r[i]);i++)t||1!==n.nodeType||x.cleanData(Ft(n)),n.parentNode&&(t&&x.contains(n.ownerDocument,n)&&_t(Ft(n,"script")),n.parentNode.removeChild(n));return this},empty:function(){var e,t=0;for(;null!=(e=this[t]);t++){1===e.nodeType&&x.cleanData(Ft(e,!1));while(e.firstChild)e.removeChild(e.firstChild);e.options&&x.nodeName(e,"select")&&(e.options.length=0)}return this},clone:function(e,t){return e=null==e?!1:e,t=null==t?e:t,this.map(function(){return x.clone(this,e,t)})},html:function(e){return x.access(this,function(e){var n=this[0]||{},r=0,i=this.length;if(e===t)return 1===n.nodeType?n.innerHTML.replace(gt,""):t;if(!("string"!=typeof e||Tt.test(e)||!x.support.htmlSerialize&&mt.test(e)||!x.support.leadingWhitespace&&yt.test(e)||At[(bt.exec(e)||["",""])[1].toLowerCase()])){e=e.replace(vt,"<$1>");try{for(;i>r;r++)n=this[r]||{},1===n.nodeType&&(x.cleanData(Ft(n,!1)),n.innerHTML=e);n=0}catch(o){}}n&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var e=x.map(this,function(e){return[e.nextSibling,e.parentNode]}),t=0;return this.domManip(arguments,function(n){var r=e[t++],i=e[t++];i&&(r&&r.parentNode!==i&&(r=this.nextSibling),x(this).remove(),i.insertBefore(n,r))},!0),t?this:this.remove()},detach:function(e){return this.remove(e,!0)},domManip:function(e,t,n){e=d.apply([],e);var r,i,o,a,s,l,u=0,c=this.length,p=this,f=c-1,h=e[0],g=x.isFunction(h);if(g||!(1>=c||"string"!=typeof h||x.support.checkClone)&&Nt.test(h))return this.each(function(r){var i=p.eq(r);g&&(e[0]=h.call(this,r,i.html())),i.domManip(e,t,n)});if(c&&(l=x.buildFragment(e,this[0].ownerDocument,!1,!n&&this),r=l.firstChild,1===l.childNodes.length&&(l=r),r)){for(a=x.map(Ft(l,"script"),Ht),o=a.length;c>u;u++)i=l,u!==f&&(i=x.clone(i,!0,!0),o&&x.merge(a,Ft(i,"script"))),t.call(this[u],i,u);if(o)for(s=a[a.length-1].ownerDocument,x.map(a,qt),u=0;o>u;u++)i=a[u],kt.test(i.type||"")&&!x._data(i,"globalEval")&&x.contains(s,i)&&(i.src?x._evalUrl(i.src):x.globalEval((i.text||i.textContent||i.innerHTML||"").replace(St,"")));l=r=null}return this}});function Lt(e,t){return x.nodeName(e,"table")&&x.nodeName(1===t.nodeType?t:t.firstChild,"tr")?e.getElementsByTagName("tbody")[0]||e.appendChild(e.ownerDocument.createElement("tbody")):e}function Ht(e){return e.type=(null!==x.find.attr(e,"type"))+"/"+e.type,e}function qt(e){var t=Et.exec(e.type);return t?e.type=t[1]:e.removeAttribute("type"),e}function _t(e,t){var n,r=0;for(;null!=(n=e[r]);r++)x._data(n,"globalEval",!t||x._data(t[r],"globalEval"))}function Mt(e,t){if(1===t.nodeType&&x.hasData(e)){var n,r,i,o=x._data(e),a=x._data(t,o),s=o.events;if(s){delete a.handle,a.events={};for(n in s)for(r=0,i=s[n].length;i>r;r++)x.event.add(t,n,s[n][r])}a.data&&(a.data=x.extend({},a.data))}}function Ot(e,t){var n,r,i;if(1===t.nodeType){if(n=t.nodeName.toLowerCase(),!x.support.noCloneEvent&&t[x.expando]){i=x._data(t);for(r in i.events)x.removeEvent(t,r,i.handle);t.removeAttribute(x.expando)}"script"===n&&t.text!==e.text?(Ht(t).text=e.text,qt(t)):"object"===n?(t.parentNode&&(t.outerHTML=e.outerHTML),x.support.html5Clone&&e.innerHTML&&!x.trim(t.innerHTML)&&(t.innerHTML=e.innerHTML)):"input"===n&&Ct.test(e.type)?(t.defaultChecked=t.checked=e.checked,t.value!==e.value&&(t.value=e.value)):"option"===n?t.defaultSelected=t.selected=e.defaultSelected:("input"===n||"textarea"===n)&&(t.defaultValue=e.defaultValue)}}x.each({appendTo:"append",prependTo:"prepend",insertBefore:"before",insertAfter:"after",replaceAll:"replaceWith"},function(e,t){x.fn[e]=function(e){var n,r=0,i=[],o=x(e),a=o.length-1;for(;a>=r;r++)n=r===a?this:this.clone(!0),x(o[r])[t](n),h.apply(i,n.get());return this.pushStack(i)}});function Ft(e,n){var r,o,a=0,s=typeof e.getElementsByTagName!==i?e.getElementsByTagName(n||"*"):typeof e.querySelectorAll!==i?e.querySelectorAll(n||"*"):t;if(!s)for(s=[],r=e.childNodes||e;null!=(o=r[a]);a++)!n||x.nodeName(o,n)?s.push(o):x.merge(s,Ft(o,n));return n===t||n&&x.nodeName(e,n)?x.merge([e],s):s}function Bt(e){Ct.test(e.type)&&(e.defaultChecked=e.checked)}x.extend({clone:function(e,t,n){var r,i,o,a,s,l=x.contains(e.ownerDocument,e);if(x.support.html5Clone||x.isXMLDoc(e)||!mt.test("<"+e.nodeName+">")?o=e.cloneNode(!0):(Dt.innerHTML=e.outerHTML,Dt.removeChild(o=Dt.firstChild)),!(x.support.noCloneEvent&&x.support.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||x.isXMLDoc(e)))for(r=Ft(o),s=Ft(e),a=0;null!=(i=s[a]);++a)r[a]&&Ot(i,r[a]);if(t)if(n)for(s=s||Ft(e),r=r||Ft(o),a=0;null!=(i=s[a]);a++)Mt(i,r[a]);else Mt(e,o);return r=Ft(o,"script"),r.length>0&&_t(r,!l&&Ft(e,"script")),r=s=i=null,o},buildFragment:function(e,t,n,r){var i,o,a,s,l,u,c,p=e.length,f=dt(t),d=[],h=0;for(;p>h;h++)if(o=e[h],o||0===o)if("object"===x.type(o))x.merge(d,o.nodeType?[o]:o);else if(wt.test(o)){s=s||f.appendChild(t.createElement("div")),l=(bt.exec(o)||["",""])[1].toLowerCase(),c=At[l]||At._default,s.innerHTML=c[1]+o.replace(vt,"<$1>")+c[2],i=c[0];while(i--)s=s.lastChild;if(!x.support.leadingWhitespace&&yt.test(o)&&d.push(t.createTextNode(yt.exec(o)[0])),!x.support.tbody){o="table"!==l||xt.test(o)?""!==c[1]||xt.test(o)?0:s:s.firstChild,i=o&&o.childNodes.length;while(i--)x.nodeName(u=o.childNodes[i],"tbody")&&!u.childNodes.length&&o.removeChild(u)}x.merge(d,s.childNodes),s.textContent="";while(s.firstChild)s.removeChild(s.firstChild);s=f.lastChild}else d.push(t.createTextNode(o));s&&f.removeChild(s),x.support.appendChecked||x.grep(Ft(d,"input"),Bt),h=0;while(o=d[h++])if((!r||-1===x.inArray(o,r))&&(a=x.contains(o.ownerDocument,o),s=Ft(f.appendChild(o),"script"),a&&_t(s),n)){i=0;while(o=s[i++])kt.test(o.type||"")&&n.push(o)}return s=null,f},cleanData:function(e,t){var n,r,o,a,s=0,l=x.expando,u=x.cache,c=x.support.deleteExpando,f=x.event.special;for(;null!=(n=e[s]);s++)if((t||x.acceptData(n))&&(o=n[l],a=o&&u[o])){if(a.events)for(r in a.events)f[r]?x.event.remove(n,r):x.removeEvent(n,r,a.handle); +u[o]&&(delete u[o],c?delete n[l]:typeof n.removeAttribute!==i?n.removeAttribute(l):n[l]=null,p.push(o))}},_evalUrl:function(e){return x.ajax({url:e,type:"GET",dataType:"script",async:!1,global:!1,"throws":!0})}}),x.fn.extend({wrapAll:function(e){if(x.isFunction(e))return this.each(function(t){x(this).wrapAll(e.call(this,t))});if(this[0]){var t=x(e,this[0].ownerDocument).eq(0).clone(!0);this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){var e=this;while(e.firstChild&&1===e.firstChild.nodeType)e=e.firstChild;return e}).append(this)}return this},wrapInner:function(e){return x.isFunction(e)?this.each(function(t){x(this).wrapInner(e.call(this,t))}):this.each(function(){var t=x(this),n=t.contents();n.length?n.wrapAll(e):t.append(e)})},wrap:function(e){var t=x.isFunction(e);return this.each(function(n){x(this).wrapAll(t?e.call(this,n):e)})},unwrap:function(){return this.parent().each(function(){x.nodeName(this,"body")||x(this).replaceWith(this.childNodes)}).end()}});var Pt,Rt,Wt,$t=/alpha\([^)]*\)/i,It=/opacity\s*=\s*([^)]*)/,zt=/^(top|right|bottom|left)$/,Xt=/^(none|table(?!-c[ea]).+)/,Ut=/^margin/,Vt=RegExp("^("+w+")(.*)$","i"),Yt=RegExp("^("+w+")(?!px)[a-z%]+$","i"),Jt=RegExp("^([+-])=("+w+")","i"),Gt={BODY:"block"},Qt={position:"absolute",visibility:"hidden",display:"block"},Kt={letterSpacing:0,fontWeight:400},Zt=["Top","Right","Bottom","Left"],en=["Webkit","O","Moz","ms"];function tn(e,t){if(t in e)return t;var n=t.charAt(0).toUpperCase()+t.slice(1),r=t,i=en.length;while(i--)if(t=en[i]+n,t in e)return t;return r}function nn(e,t){return e=t||e,"none"===x.css(e,"display")||!x.contains(e.ownerDocument,e)}function rn(e,t){var n,r,i,o=[],a=0,s=e.length;for(;s>a;a++)r=e[a],r.style&&(o[a]=x._data(r,"olddisplay"),n=r.style.display,t?(o[a]||"none"!==n||(r.style.display=""),""===r.style.display&&nn(r)&&(o[a]=x._data(r,"olddisplay",ln(r.nodeName)))):o[a]||(i=nn(r),(n&&"none"!==n||!i)&&x._data(r,"olddisplay",i?n:x.css(r,"display"))));for(a=0;s>a;a++)r=e[a],r.style&&(t&&"none"!==r.style.display&&""!==r.style.display||(r.style.display=t?o[a]||"":"none"));return e}x.fn.extend({css:function(e,n){return x.access(this,function(e,n,r){var i,o,a={},s=0;if(x.isArray(n)){for(o=Rt(e),i=n.length;i>s;s++)a[n[s]]=x.css(e,n[s],!1,o);return a}return r!==t?x.style(e,n,r):x.css(e,n)},e,n,arguments.length>1)},show:function(){return rn(this,!0)},hide:function(){return rn(this)},toggle:function(e){var t="boolean"==typeof e;return this.each(function(){(t?e:nn(this))?x(this).show():x(this).hide()})}}),x.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=Wt(e,"opacity");return""===n?"1":n}}}},cssNumber:{columnCount:!0,fillOpacity:!0,fontWeight:!0,lineHeight:!0,opacity:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{"float":x.support.cssFloat?"cssFloat":"styleFloat"},style:function(e,n,r,i){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var o,a,s,l=x.camelCase(n),u=e.style;if(n=x.cssProps[l]||(x.cssProps[l]=tn(u,l)),s=x.cssHooks[n]||x.cssHooks[l],r===t)return s&&"get"in s&&(o=s.get(e,!1,i))!==t?o:u[n];if(a=typeof r,"string"===a&&(o=Jt.exec(r))&&(r=(o[1]+1)*o[2]+parseFloat(x.css(e,n)),a="number"),!(null==r||"number"===a&&isNaN(r)||("number"!==a||x.cssNumber[l]||(r+="px"),x.support.clearCloneStyle||""!==r||0!==n.indexOf("background")||(u[n]="inherit"),s&&"set"in s&&(r=s.set(e,r,i))===t)))try{u[n]=r}catch(c){}}},css:function(e,n,r,i){var o,a,s,l=x.camelCase(n);return n=x.cssProps[l]||(x.cssProps[l]=tn(e.style,l)),s=x.cssHooks[n]||x.cssHooks[l],s&&"get"in s&&(a=s.get(e,!0,r)),a===t&&(a=Wt(e,n,i)),"normal"===a&&n in Kt&&(a=Kt[n]),""===r||r?(o=parseFloat(a),r===!0||x.isNumeric(o)?o||0:a):a}}),e.getComputedStyle?(Rt=function(t){return e.getComputedStyle(t,null)},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s.getPropertyValue(n)||s[n]:t,u=e.style;return s&&(""!==l||x.contains(e.ownerDocument,e)||(l=x.style(e,n)),Yt.test(l)&&Ut.test(n)&&(i=u.width,o=u.minWidth,a=u.maxWidth,u.minWidth=u.maxWidth=u.width=l,l=s.width,u.width=i,u.minWidth=o,u.maxWidth=a)),l}):a.documentElement.currentStyle&&(Rt=function(e){return e.currentStyle},Wt=function(e,n,r){var i,o,a,s=r||Rt(e),l=s?s[n]:t,u=e.style;return null==l&&u&&u[n]&&(l=u[n]),Yt.test(l)&&!zt.test(n)&&(i=u.left,o=e.runtimeStyle,a=o&&o.left,a&&(o.left=e.currentStyle.left),u.left="fontSize"===n?"1em":l,l=u.pixelLeft+"px",u.left=i,a&&(o.left=a)),""===l?"auto":l});function on(e,t,n){var r=Vt.exec(t);return r?Math.max(0,r[1]-(n||0))+(r[2]||"px"):t}function an(e,t,n,r,i){var o=n===(r?"border":"content")?4:"width"===t?1:0,a=0;for(;4>o;o+=2)"margin"===n&&(a+=x.css(e,n+Zt[o],!0,i)),r?("content"===n&&(a-=x.css(e,"padding"+Zt[o],!0,i)),"margin"!==n&&(a-=x.css(e,"border"+Zt[o]+"Width",!0,i))):(a+=x.css(e,"padding"+Zt[o],!0,i),"padding"!==n&&(a+=x.css(e,"border"+Zt[o]+"Width",!0,i)));return a}function sn(e,t,n){var r=!0,i="width"===t?e.offsetWidth:e.offsetHeight,o=Rt(e),a=x.support.boxSizing&&"border-box"===x.css(e,"boxSizing",!1,o);if(0>=i||null==i){if(i=Wt(e,t,o),(0>i||null==i)&&(i=e.style[t]),Yt.test(i))return i;r=a&&(x.support.boxSizingReliable||i===e.style[t]),i=parseFloat(i)||0}return i+an(e,t,n||(a?"border":"content"),r,o)+"px"}function ln(e){var t=a,n=Gt[e];return n||(n=un(e,t),"none"!==n&&n||(Pt=(Pt||x("