diff --git a/personal/nuzlocke-list/document.aux b/personal/nuzlocke-list/document.aux deleted file mode 100644 index e69de29..0000000 diff --git a/personal/nuzlocke-list/document.idx b/personal/nuzlocke-list/document.idx deleted file mode 100644 index e69de29..0000000 diff --git a/personal/nuzlocke-list/document.log b/personal/nuzlocke-list/document.log deleted file mode 100644 index 82f2cec..0000000 --- a/personal/nuzlocke-list/document.log +++ /dev/null @@ -1,415 +0,0 @@ -This is pdfTeX, Version 3.14159265-2.6-1.40.21 (MiKTeX 21.1) (preloaded format=pdflatex 2021.2.9) 23 FEB 2021 18:52 -entering extended mode -**Z:/Projects/GitHub/homework-and-stuffs/personal/nuzlocke-list/document.tex -(Z:/Projects/GitHub/homework-and-stuffs/personal/nuzlocke-list/document.tex -LaTeX2e <2020-10-01> patch level 4 -L3 programming layer <2021-01-09> xparse <2020-03-03> -(E:\Applications\MiKTeX\tex/latex/base\article.cls -Document Class: article 2020/04/10 v1.4m Standard LaTeX document class -(E:\Applications\MiKTeX\tex/latex/base\size10.clo -File: size10.clo 2020/04/10 v1.4m Standard LaTeX file (size option) -) -\c@part=\count177 -\c@section=\count178 -\c@subsection=\count179 -\c@subsubsection=\count180 -\c@paragraph=\count181 -\c@subparagraph=\count182 -\c@figure=\count183 -\c@table=\count184 -\abovecaptionskip=\skip47 -\belowcaptionskip=\skip48 -\bibindent=\dimen138 -) -(E:\Applications\MiKTeX\tex/latex/geometry\geometry.sty -Package: geometry 2020/01/02 v5.9 Page Geometry - -(E:\Applications\MiKTeX\tex/latex/graphics\keyval.sty -Package: keyval 2014/10/28 v1.15 key=value parser (DPC) -\KV@toks@=\toks15 -) -(E:\Applications\MiKTeX\tex/generic/iftex\ifvtex.sty -Package: ifvtex 2019/10/25 v1.7 ifvtex legacy package. Use iftex instead. - -(E:\Applications\MiKTeX\tex/generic/iftex\iftex.sty -Package: iftex 2020/03/06 v1.0d TeX engine tests -)) -\Gm@cnth=\count185 -\Gm@cntv=\count186 -\c@Gm@tempcnt=\count187 -\Gm@bindingoffset=\dimen139 -\Gm@wd@mp=\dimen140 -\Gm@odd@mp=\dimen141 -\Gm@even@mp=\dimen142 -\Gm@layoutwidth=\dimen143 -\Gm@layoutheight=\dimen144 -\Gm@layouthoffset=\dimen145 -\Gm@layoutvoffset=\dimen146 -\Gm@dimlist=\toks16 - -(E:\Applications\MiKTeX\tex/latex/geometry\geometry.cfg)) -(E:\Applications\MiKTeX\tex/latex/graphics\graphicx.sty -Package: graphicx 2020/09/09 v1.2b Enhanced LaTeX Graphics (DPC,SPQR) - -(E:\Applications\MiKTeX\tex/latex/graphics\graphics.sty -Package: graphics 2020/08/30 v1.4c Standard LaTeX Graphics (DPC,SPQR) - -(E:\Applications\MiKTeX\tex/latex/graphics\trig.sty -Package: trig 2016/01/03 v1.10 sin cos tan (DPC) -) -(E:\Applications\MiKTeX\tex/latex/graphics-cfg\graphics.cfg -File: graphics.cfg 2016/06/04 v1.11 sample graphics configuration -) -Package graphics Info: Driver file: pdftex.def on input line 105. - -(E:\Applications\MiKTeX\tex/latex/graphics-def\pdftex.def -File: pdftex.def 2020/10/05 v1.2a Graphics/color driver for pdftex -)) -\Gin@req@height=\dimen147 -\Gin@req@width=\dimen148 -) -(E:\Applications\MiKTeX\tex/latex/hyperref\hyperref.sty -Package: hyperref 2020-05-15 v7.00e Hypertext links for LaTeX - -(E:\Applications\MiKTeX\tex/generic/ltxcmds\ltxcmds.sty -Package: ltxcmds 2020-05-10 v1.25 LaTeX kernel commands for general use (HO) -) -(E:\Applications\MiKTeX\tex/generic/pdftexcmds\pdftexcmds.sty -Package: pdftexcmds 2020-06-27 v0.33 Utility functions of pdfTeX for LuaTeX (HO -) - -(E:\Applications\MiKTeX\tex/generic/infwarerr\infwarerr.sty -Package: infwarerr 2019/12/03 v1.5 Providing info/warning/error messages (HO) -) -Package pdftexcmds Info: \pdf@primitive is available. -Package pdftexcmds Info: \pdf@ifprimitive is available. -Package pdftexcmds Info: \pdfdraftmode found. -) -(E:\Applications\MiKTeX\tex/generic/kvsetkeys\kvsetkeys.sty -Package: kvsetkeys 2019/12/15 v1.18 Key value parser (HO) -) -(E:\Applications\MiKTeX\tex/generic/kvdefinekeys\kvdefinekeys.sty -Package: kvdefinekeys 2019-12-19 v1.6 Define keys (HO) -) -(E:\Applications\MiKTeX\tex/generic/pdfescape\pdfescape.sty -Package: pdfescape 2019/12/09 v1.15 Implements pdfTeX's escape features (HO) -) -(E:\Applications\MiKTeX\tex/latex/hycolor\hycolor.sty -Package: hycolor 2020-01-27 v1.10 Color options for hyperref/bookmark (HO) -) -(E:\Applications\MiKTeX\tex/latex/letltxmacro\letltxmacro.sty -Package: letltxmacro 2019/12/03 v1.6 Let assignment for LaTeX macros (HO) -) -(E:\Applications\MiKTeX\tex/latex/auxhook\auxhook.sty -Package: auxhook 2019-12-17 v1.6 Hooks for auxiliary files (HO) -) -(E:\Applications\MiKTeX\tex/latex/kvoptions\kvoptions.sty -Package: kvoptions 2020-10-07 v3.14 Key value format for package options (HO) -) -\@linkdim=\dimen149 -\Hy@linkcounter=\count188 -\Hy@pagecounter=\count189 - -(E:\Applications\MiKTeX\tex/latex/hyperref\pd1enc.def -File: pd1enc.def 2020-05-15 v7.00e Hyperref: PDFDocEncoding definition (HO) -Now handling font encoding PD1 ... -... no UTF-8 mapping file for font encoding PD1 -) -(E:\Applications\MiKTeX\tex/generic/intcalc\intcalc.sty -Package: intcalc 2019/12/15 v1.3 Expandable calculations with integers (HO) -) -(E:\Applications\MiKTeX\tex/generic/etexcmds\etexcmds.sty -Package: etexcmds 2019/12/15 v1.7 Avoid name clashes with e-TeX commands (HO) -) -\Hy@SavedSpaceFactor=\count190 -Package hyperref Info: Hyper figures OFF on input line 4464. -Package hyperref Info: Link nesting OFF on input line 4469. -Package hyperref Info: Hyper index ON on input line 4472. -Package hyperref Info: Plain pages OFF on input line 4479. -Package hyperref Info: Backreferencing OFF on input line 4484. -Package hyperref Info: Implicit mode ON; LaTeX internals redefined. -Package hyperref Info: Bookmarks ON on input line 4717. -\c@Hy@tempcnt=\count191 - -(E:\Applications\MiKTeX\tex/latex/url\url.sty -\Urlmuskip=\muskip16 -Package: url 2013/09/16 ver 3.4 Verb mode for urls, etc. -) -LaTeX Info: Redefining \url on input line 5076. -\XeTeXLinkMargin=\dimen150 - -(E:\Applications\MiKTeX\tex/generic/bitset\bitset.sty -Package: bitset 2019/12/09 v1.3 Handle bit-vector datatype (HO) - -(E:\Applications\MiKTeX\tex/generic/bigintcalc\bigintcalc.sty -Package: bigintcalc 2019/12/15 v1.5 Expandable calculations on big integers (HO -) -)) -\Fld@menulength=\count192 -\Field@Width=\dimen151 -\Fld@charsize=\dimen152 -Package hyperref Info: Hyper figures OFF on input line 6347. -Package hyperref Info: Link nesting OFF on input line 6352. -Package hyperref Info: Hyper index ON on input line 6355. -Package hyperref Info: backreferencing OFF on input line 6362. -Package hyperref Info: Link coloring OFF on input line 6367. -Package hyperref Info: Link coloring with OCG OFF on input line 6372. -Package hyperref Info: PDF/A mode OFF on input line 6377. -LaTeX Info: Redefining \ref on input line 6417. -LaTeX Info: Redefining \pageref on input line 6421. - -(E:\Applications\MiKTeX\tex/latex/base\atbegshi-ltx.sty -Package: atbegshi-ltx 2020/08/17 v1.0a Emulation of the original atbegshi packa -ge -with kernel methods -) -\Hy@abspage=\count193 -\c@Item=\count194 -\c@Hfootnote=\count195 -) -Package hyperref Info: Driver (autodetected): hpdftex. - -(E:\Applications\MiKTeX\tex/latex/hyperref\hpdftex.def -File: hpdftex.def 2020-05-15 v7.00e Hyperref driver for pdfTeX - -(E:\Applications\MiKTeX\tex/latex/base\atveryend-ltx.sty -Package: atveryend-ltx 2020/08/19 v1.0a Emulation of the original atvery packag -e -with kernel methods -) -\Fld@listcount=\count196 -\c@bookmark@seq@number=\count197 - -(E:\Applications\MiKTeX\tex/latex/rerunfilecheck\rerunfilecheck.sty -Package: rerunfilecheck 2019/12/05 v1.9 Rerun checks for auxiliary files (HO) - -(E:\Applications\MiKTeX\tex/generic/uniquecounter\uniquecounter.sty -Package: uniquecounter 2019/12/15 v1.4 Provide unlimited unique counter (HO) -) -Package uniquecounter Info: New unique counter `rerunfilecheck' on input line 2 -86. -) -\Hy@SectionHShift=\skip49 -) -(E:\Applications\MiKTeX\tex/latex/listings\listings.sty -\lst@mode=\count198 -\lst@gtempboxa=\box47 -\lst@token=\toks17 -\lst@length=\count199 -\lst@currlwidth=\dimen153 -\lst@column=\count266 -\lst@pos=\count267 -\lst@lostspace=\dimen154 -\lst@width=\dimen155 -\lst@newlines=\count268 -\lst@lineno=\count269 -\lst@maxwidth=\dimen156 - -(E:\Applications\MiKTeX\tex/latex/listings\lstmisc.sty -File: lstmisc.sty 2020/03/24 1.8d (Carsten Heinz) -\c@lstnumber=\count270 -\lst@skipnumbers=\count271 -\lst@framebox=\box48 -) -(E:\Applications\MiKTeX\tex/latex/listings\listings.cfg -File: listings.cfg 2020/03/24 1.8d listings configuration -)) -Package: listings 2020/03/24 1.8d (Carsten Heinz) - -(E:\Applications\MiKTeX\tex/latex/amsfonts\amssymb.sty -Package: amssymb 2013/01/14 v3.01 AMS font symbols - -(E:\Applications\MiKTeX\tex/latex/amsfonts\amsfonts.sty -Package: amsfonts 2013/01/14 v3.01 Basic AMSFonts support -\@emptytoks=\toks18 -\symAMSa=\mathgroup4 -\symAMSb=\mathgroup5 -LaTeX Font Info: Redeclaring math symbol \hbar on input line 98. -LaTeX Font Info: Overwriting math alphabet `\mathfrak' in version `bold' -(Font) U/euf/m/n --> U/euf/b/n on input line 106. -)) -(E:\Applications\MiKTeX\tex/latex/mwe\mwe.sty -Package: mwe 2018/03/30 v0.5 Package to support minimal working examples (MWE) - -(E:\Applications\MiKTeX\tex/latex/lipsum\lipsum.sty -(E:\Applications\MiKTeX\tex/latex/l3kernel\expl3.sty -Package: expl3 2021-01-09 L3 programming layer (loader) - -(E:\Applications\MiKTeX\tex/latex/l3backend\l3backend-pdftex.def -File: l3backend-pdftex.def 2021-01-09 L3 backend support: PDF output (pdfTeX) -\l__kernel_color_stack_int=\count272 -\l__pdf_internal_box=\box49 -)) -(E:\Applications\MiKTeX\tex/latex/l3packages/xparse\xparse.sty -Package: xparse 2020-10-27 L3 Experimental document command parser - -(E:\Applications\MiKTeX\tex/latex/l3packages/xparse\xparse-generic.tex)) -Package: lipsum 2019/01/02 v2.2 150 paragraphs of Lorem Ipsum dummy text - -(E:\Applications\MiKTeX\tex/latex/lipsum\lipsum.ltd.tex -File: lipsum.ltd.tex 2019/01/02 v2.2 The Lorem ipsum dummy text -)) -(E:\Applications\MiKTeX\tex/latex/blindtext\blindtext.sty -Package: blindtext 2012/01/06 V2.0 blindtext-Package - -(E:\Applications\MiKTeX\tex/latex/latex-tools\xspace.sty -Package: xspace 2014/10/28 v1.13 Space after command names (DPC,MH) -) -\c@blindtext=\count273 -\c@Blindtext=\count274 -\c@blind@countparstart=\count275 -\blind@countxx=\count276 -\blindtext@numBlindtext=\count277 -\blind@countyy=\count278 -\c@blindlist=\count279 -\c@blindlistlevel=\count280 -\c@blindlist@level=\count281 -\blind@listitem=\count282 -\c@blind@listcount=\count283 -\c@blind@levelcount=\count284 -\blind@mathformula=\count285 -\blind@Mathformula=\count286 -\c@blind@randomcount=\count287 -\c@blind@randommax=\count288 -\c@blind@pangramcount=\count289 -\c@blind@pangrammax=\count290 -)) -(E:\Applications\MiKTeX\tex/latex/enumitem\enumitem.sty -Package: enumitem 2019/06/20 v3.9 Customized lists -\labelindent=\skip50 -\enit@outerparindent=\dimen157 -\enit@toks=\toks19 -\enit@inbox=\box50 -\enit@count@id=\count291 -\enitdp@description=\count292 -) -(E:\Applications\MiKTeX\tex/latex/imakeidx\imakeidx.sty -Package: imakeidx 2016/10/15 v1.3e Package for typesetting indices in a synchro -nous mode - -(E:\Applications\MiKTeX\tex/latex/xkeyval\xkeyval.sty -Package: xkeyval 2020/11/20 v2.8 package option processing (HA) - -(E:\Applications\MiKTeX\tex/generic/xkeyval\xkeyval.tex -(E:\Applications\MiKTeX\tex/generic/xkeyval\xkvutils.tex -\XKV@toks=\toks20 -\XKV@tempa@toks=\toks21 -) -\XKV@depth=\count293 -File: xkeyval.tex 2014/12/03 v2.7a key=value parser (HA) -)) -(E:\Applications\MiKTeX\tex/generic/iftex\ifxetex.sty -Package: ifxetex 2019/10/25 v0.7 ifxetex legacy package. Use iftex instead. -) -(E:\Applications\MiKTeX\tex/generic/iftex\ifluatex.sty -Package: ifluatex 2019/10/25 v1.5 ifluatex legacy package. Use iftex instead. -) -(E:\Applications\MiKTeX\tex/latex/latex-tools\multicol.sty -Package: multicol 2019/12/09 v1.8y multicolumn formatting (FMi) -\c@tracingmulticols=\count294 -\mult@box=\box51 -\multicol@leftmargin=\dimen158 -\c@unbalance=\count295 -\c@collectmore=\count296 -\doublecol@number=\count297 -\multicoltolerance=\count298 -\multicolpretolerance=\count299 -\full@width=\dimen159 -\page@free=\dimen160 -\premulticols=\dimen161 -\postmulticols=\dimen162 -\multicolsep=\skip51 -\multicolbaselineskip=\skip52 -\partial@page=\box52 -\last@line=\box53 -\maxbalancingoverflow=\dimen163 -\mult@rightbox=\box54 -\mult@grightbox=\box55 -\mult@gfirstbox=\box56 -\mult@firstbox=\box57 -\@tempa=\box58 -\@tempa=\box59 -\@tempa=\box60 -\@tempa=\box61 -\@tempa=\box62 -\@tempa=\box63 -\@tempa=\box64 -\@tempa=\box65 -\@tempa=\box66 -\@tempa=\box67 -\@tempa=\box68 -\@tempa=\box69 -\@tempa=\box70 -\@tempa=\box71 -\@tempa=\box72 -\@tempa=\box73 -\@tempa=\box74 -\@tempa=\box75 -\@tempa=\box76 -\@tempa=\box77 -\@tempa=\box78 -\@tempa=\box79 -\@tempa=\box80 -\@tempa=\box81 -\@tempa=\box82 -\@tempa=\box83 -\@tempa=\box84 -\@tempa=\box85 -\@tempa=\box86 -\@tempa=\box87 -\@tempa=\box88 -\@tempa=\box89 -\@tempa=\box90 -\@tempa=\box91 -\@tempa=\box92 -\@tempa=\box93 -\@tempa=\box94 -\c@minrows=\count300 -\c@columnbadness=\count301 -\c@finalcolumnbadness=\count302 -\last@try=\dimen164 -\multicolovershoot=\dimen165 -\multicolundershoot=\dimen166 -\mult@nat@firstbox=\box95 -\colbreak@box=\box96 -\mc@col@check@num=\count303 -)) - -! LaTeX Error: File `elsarticle.sty' not found. - -Type X to quit or to proceed, -or enter new name. (Default extension: sty) - -Enter file name: output.html - -! LaTeX Error: File `output.html' not found. - -Type X to quit or to proceed, -or enter new name. (Default extension: html) - -Enter file name: output - -! LaTeX Error: File `output.html' not found. - -Type X to quit or to proceed, -or enter new name. (Default extension: html) - -Enter file name: x - -! Emergency stop. - - -l.18 - -*** (cannot \read from terminal in nonstop modes) - - -Here is how much of TeX's memory you used: - 7841 strings out of 479618 - 121034 string characters out of 2875417 - 578295 words of memory out of 3000000 - 24913 multiletter control sequences out of 15000+200000 - 403430 words of font info for 27 fonts, out of 3000000 for 9000 - 1141 hyphenation exceptions out of 8191 - 81i,0n,89p,298b,264s stack positions out of 5000i,500n,10000p,200000b,50000s -! ==> Fatal error occurred, no output PDF file produced! diff --git a/personal/nuzlocke-list/document.out b/personal/nuzlocke-list/document.out deleted file mode 100644 index e69de29..0000000 diff --git a/personal/nuzlocke-list/document.toc b/personal/nuzlocke-list/document.toc deleted file mode 100644 index e69de29..0000000 diff --git a/personal/nuzlocke-list/document.pdf b/personal/pkmn-nuzlocke-wiki/document.pdf similarity index 100% rename from personal/nuzlocke-list/document.pdf rename to personal/pkmn-nuzlocke-wiki/document.pdf diff --git a/personal/nuzlocke-list/document.synctex(busy) b/personal/pkmn-nuzlocke-wiki/document.synctex(busy) similarity index 100% rename from personal/nuzlocke-list/document.synctex(busy) rename to personal/pkmn-nuzlocke-wiki/document.synctex(busy) diff --git a/personal/nuzlocke-list/document.synctex.gz b/personal/pkmn-nuzlocke-wiki/document.synctex.gz similarity index 100% rename from personal/nuzlocke-list/document.synctex.gz rename to personal/pkmn-nuzlocke-wiki/document.synctex.gz diff --git a/personal/nuzlocke-list/document.tex b/personal/pkmn-nuzlocke-wiki/document.tex similarity index 100% rename from personal/nuzlocke-list/document.tex rename to personal/pkmn-nuzlocke-wiki/document.tex diff --git a/personal/nuzlocke-list/images/logo.png b/personal/pkmn-nuzlocke-wiki/images/logo.png similarity index 100% rename from personal/nuzlocke-list/images/logo.png rename to personal/pkmn-nuzlocke-wiki/images/logo.png diff --git a/personal/nuzlocke-list/sources.bib b/personal/pkmn-nuzlocke-wiki/sources.bib similarity index 100% rename from personal/nuzlocke-list/sources.bib rename to personal/pkmn-nuzlocke-wiki/sources.bib diff --git a/school/cpsc466/current-IMP.xlsx b/school/cpsc466/current-IMP.xlsx new file mode 100644 index 0000000..fdf3583 Binary files /dev/null and b/school/cpsc466/current-IMP.xlsx differ diff --git a/school/cpsc466/week-6/sdp-wip.tex b/school/cpsc466/week-6/sdp-wip.tex new file mode 100644 index 0000000..1f6a623 --- /dev/null +++ b/school/cpsc466/week-6/sdp-wip.tex @@ -0,0 +1,30 @@ +\documentclass[12pt]{article} +\usepackage[a4paper, total={7in, 10in}]{geometry} + +\usepackage{graphicx} +\usepackage{abstract} +\usepackage{hyperref} +\usepackage{listings} +\usepackage{amssymb} +\usepackage{fancyhdr} +\usepackage{adjustbox} + + +\begin{document} + +\section{Project Organization / Structure} + \subsection{Process Model} + This project utilizes an iterative development and deployment using SCRUM methodologies. + \begin{table}[!hbt] + \centering + \begin{tabular}{|l|l|l|l|l|} + \hline + \textbf{Crash Avoidance System} & Planned Completion Date & Placed Under Change Control & Deliverable to Customer? & People Who Must Sign Off \\ \hline + Develop Crash Avoidance System (CAS) & December 31st, 2021 & No & & Project Manager Engineering Lead, Documentation Lead \\ \hline + Develop In-Flight Test of \\Crash Avoidance System (IFTCAS) & December 31st, 2022 & No & & Project Manager Engineering Lead, Documentation Lead \\ \hline + Incorporate Test Results and \\Associated Design Changes \\into Final System & December 31st, 2023 & No & & Project Manager Engineering Lead, Documentation Lead, Customer Approval \\ \hline + TBD & TBD & TBD & TBD & TBD \\ \hline + \end{tabular} + \end{table} + +\end{document} \ No newline at end of file diff --git a/school/cpsc481/project1/.gitignore b/school/cpsc481/project1/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/school/cpsc481/project1/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/school/cpsc481/project1/LICENSE b/school/cpsc481/project1/LICENSE new file mode 100644 index 0000000..407d5a9 --- /dev/null +++ b/school/cpsc481/project1/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Jared Dyreson + +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. diff --git a/school/cpsc481/project1/README.md b/school/cpsc481/project1/README.md new file mode 100644 index 0000000..138c017 --- /dev/null +++ b/school/cpsc481/project1/README.md @@ -0,0 +1,2 @@ +# pacman-ai +A Pacman maze solving project for CS 481 diff --git a/school/cpsc481/project1/commit-credit.txt b/school/cpsc481/project1/commit-credit.txt new file mode 100644 index 0000000..8d01dcd --- /dev/null +++ b/school/cpsc481/project1/commit-credit.txt @@ -0,0 +1 @@ +https://github.com/JaredDyreson/pacman-ai/tree/82c7c34645774fa4ac86170e79805490e0ead724 \ No newline at end of file diff --git a/school/cpsc481/project1/src/VERSION b/school/cpsc481/project1/src/VERSION new file mode 100644 index 0000000..6af849e --- /dev/null +++ b/school/cpsc481/project1/src/VERSION @@ -0,0 +1 @@ +v1.002 diff --git a/school/cpsc481/project1/src/algorithms/backtracking/main.py b/school/cpsc481/project1/src/algorithms/backtracking/main.py new file mode 100644 index 0000000..0a99037 --- /dev/null +++ b/school/cpsc481/project1/src/algorithms/backtracking/main.py @@ -0,0 +1,88 @@ +""" +Example implementation of backtracking search in Python +""" + +class node: + def __init__(self, label: str): + if(not isinstance(label, str)): + raise ValueError + self.label = label + + def __hash__(self): + return hash(self.label) + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +children = ["A", "B", "C", "D"] +d = ["A"] +s = ["B"] +nsl = ["C"] + +def l_difference(*args): + if not(all([isinstance(i, list) for i in args])): + raise ValueError + + container = [] + for element in args: + subcontainer = [i for i in element if i not in container] + container.extend(subcontainer) + return container + +def l_union(a: list, b: list): + # TODO : DUPLICATE CODE + + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + +def backtracking(starting: str, GD: list) -> list: + if(not isinstance(starting, str) or + not isinstance(GD, list)): + raise ValueError + + state_list, new_state_list = [starting], [starting] + dead_ends = [] + current_state = starting + if(current_state in GD): + return state_list + + children = graph[current_state] + if(not children) + +# open_nodes = ["A"] + +# state_list = open_nodes +# new_state_list = open_nodes +# dead_ends = [] +# current_state = "A" +# goal_states = ["X"] + +while(state_list): + # current_state = state_list.pop() + print(current_state) + if(current_state in goal_states): + print(f'[complete] {state_list}') # should be return + children = graph[current_state] + if(not children): + while(state_list and current_state == state_list[0]): + dead_ends.append(current_state) + state_list.pop() + new_state_list.pop() + current_state = new_state_list[0] + state_list.append(current_state) + else: + new_state_list.extend(children) + current_state = new_state_list[0] + state_list.append(current_state) diff --git a/school/cpsc481/project1/src/algorithms/bfs/main.py b/school/cpsc481/project1/src/algorithms/bfs/main.py new file mode 100644 index 0000000..9d6e7ae --- /dev/null +++ b/school/cpsc481/project1/src/algorithms/bfs/main.py @@ -0,0 +1,61 @@ +""" +Example implementation of BFS in Python +""" + +import unittest + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +def l_union(a: list, b: list): + # TODO : DUPLICATE CODE + + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + + +def bfs(G: dict, S: list, GD: list) -> bool: # -> (TYPE BEING RETURNED) + # type enforcement + if(not isinstance(S, list) or # open nodes + not isinstance(GD, list) or # acceptance states + not isinstance(G, dict)): # graph in question + raise ValueError + + X = None + open_nodes = S + goal_states = GD + closed = [] + + while(open_nodes): + X = open_nodes.pop() + if(X in goal_states): + return True + + children = l_union(graph[X], open_nodes) + open_nodes.extend(children) # append the contents of the lookup while removing duplicates from both containers + closed.append(X) + + return False + +class TestBFS(unittest.TestCase): + def test_bfs_valid_dest(self): + S, GD = ["A"], ["X"] + self.assertTrue(bfs(graph, S, GD)) + def test_bfs_invalid_dest(self): + S, GD = ["A"], ["Z"] + self.assertFalse(bfs(graph, S, GD)) + +if __name__ == '__main__': + unittest.main() diff --git a/school/cpsc481/project1/src/algorithms/dfs/main.py b/school/cpsc481/project1/src/algorithms/dfs/main.py new file mode 100644 index 0000000..9ef18ee --- /dev/null +++ b/school/cpsc481/project1/src/algorithms/dfs/main.py @@ -0,0 +1,60 @@ +""" +Example implementation of DFS in Python + +Components include: + +X <- stores current state +open <- all nodes that we are allowed to push onto the stack +closed <- can no longer travel to + +Please refer to L02, part 2 at 12:45 for further details +""" + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +def l_union(a: list, b: list): + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + +# parameters have their desired types +def dfs(G: dict, S: list, GD: list) -> bool: # -> (TYPE BEING RETURNED) + # type enforcement + if(not isinstance(S, list) or + not isinstance(GD, list) or + not isinstance(G, dict)): + raise ValueError + + X = None + open_nodes = S # renaming + goal_nodes = GD # renaming + closed = [] + + while(open_nodes): # while len(open_nodes) > 0, python is cool + X = open_nodes.pop() + if(X in goal_nodes): + return True + children = l_union(graph[X], open_nodes) + open_nodes[:0] = children # prepend the contents of the lookup while removing duplicates from both containers + closed.append(X) # current node exhausted + + return False + +resultant = dfs(graph, ["A"], ["X"]) + +if(resultant): + print("[INFO] We found a result") +else: + print("[ERROR] We could not find a result") diff --git a/school/cpsc481/project1/src/autograder.py b/school/cpsc481/project1/src/autograder.py new file mode 100644 index 0000000..21f7d3b --- /dev/null +++ b/school/cpsc481/project1/src/autograder.py @@ -0,0 +1,358 @@ +# autograder.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# imports from python standard library +import grading +import imp +import optparse +import os +import re +import sys +import projectParams +import random +random.seed(0) +try: + from pacman import GameState +except: + pass + +# register arguments and set default values +def readCommand(argv): + parser = optparse.OptionParser(description = 'Run public tests on student code') + parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, muteOutput=False, printTestCase=False, noGraphics=False) + parser.add_option('--test-directory', + dest = 'testRoot', + default = 'test_cases', + help = 'Root test directory which contains subdirectories corresponding to each question') + parser.add_option('--student-code', + dest = 'studentCode', + default = projectParams.STUDENT_CODE_DEFAULT, + help = 'comma separated list of student code files') + parser.add_option('--code-directory', + dest = 'codeRoot', + default = "", + help = 'Root directory containing the student and testClass code') + parser.add_option('--test-case-code', + dest = 'testCaseCode', + default = projectParams.PROJECT_TEST_CLASSES, + help = 'class containing testClass classes for this project') + parser.add_option('--generate-solutions', + dest = 'generateSolutions', + action = 'store_true', + help = 'Write solutions generated to .solution file') + parser.add_option('--edx-output', + dest = 'edxOutput', + action = 'store_true', + help = 'Generate edX output files') + parser.add_option('--gradescope-output', + dest = 'gsOutput', + action = 'store_true', + help = 'Generate GradeScope output files') + parser.add_option('--mute', + dest = 'muteOutput', + action = 'store_true', + help = 'Mute output from executing tests') + parser.add_option('--print-tests', '-p', + dest = 'printTestCase', + action = 'store_true', + help = 'Print each test case before running them.') + parser.add_option('--test', '-t', + dest = 'runTest', + default = None, + help = 'Run one particular test. Relative to test root.') + parser.add_option('--question', '-q', + dest = 'gradeQuestion', + default = None, + help = 'Grade one particular question.') + parser.add_option('--no-graphics', + dest = 'noGraphics', + action = 'store_true', + help = 'No graphics display for pacman games.') + (options, args) = parser.parse_args(argv) + return options + + +# confirm we should author solution files +def confirmGenerate(): + print('WARNING: this action will overwrite any solution files.') + print('Are you sure you want to proceed? (yes/no)') + while True: + ans = sys.stdin.readline().strip() + if ans == 'yes': + break + elif ans == 'no': + sys.exit(0) + else: + print('please answer either "yes" or "no"') + + +# TODO: Fix this so that it tracebacks work correctly +# Looking at source of the traceback module, presuming it works +# the same as the intepreters, it uses co_filename. This is, +# however, a readonly attribute. +def setModuleName(module, filename): + functionType = type(confirmGenerate) + classType = type(optparse.Option) + + for i in dir(module): + o = getattr(module, i) + if hasattr(o, '__file__'): continue + + if type(o) == functionType: + setattr(o, '__file__', filename) + elif type(o) == classType: + setattr(o, '__file__', filename) + # TODO: assign member __file__'s? + #print(i, type(o)) + + +#from cStringIO import StringIO + +def loadModuleString(moduleSource): + # Below broken, imp doesn't believe its being passed a file: + # ValueError: load_module arg#2 should be a file or None + # + #f = StringIO(moduleCodeDict[k]) + #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE)) + tmp = imp.new_module(k) + exec(moduleCodeDict[k] in tmp.__dict__) + setModuleName(tmp, k) + return tmp + +import py_compile + +def loadModuleFile(moduleName, filePath): + with open(filePath, 'r') as f: + return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE)) + + +def readFile(path, root=""): + "Read file from disk at specified path and return as string" + with open(os.path.join(root, path), 'r') as handle: + return handle.read() + + +####################################################################### +# Error Hint Map +####################################################################### + +# TODO: use these +ERROR_HINT_MAP = { + 'q1': { + "": """ + We noticed that your project threw an IndexError on q1. + While many things may cause this, it may have been from + assuming a certain number of successors from a state space + or assuming a certain number of actions available from a given + state. Try making your code more general (no hardcoded indices) + and submit again! + """ + }, + 'q3': { + "": """ + We noticed that your project threw an AttributeError on q3. + While many things may cause this, it may have been from assuming + a certain size or structure to the state space. For example, if you have + a line of code assuming that the state is (x, y) and we run your code + on a state space with (x, y, z), this error could be thrown. Try + making your code more general and submit again! + + """ + } +} + +import pprint + +def splitStrings(d): + d2 = dict(d) + for k in d: + if k[0:2] == "__": + del d2[k] + continue + if d2[k].find("\n") >= 0: + d2[k] = d2[k].split("\n") + return d2 + + +def printTest(testDict, solutionDict): + pp = pprint.PrettyPrinter(indent=4) + print("Test case:") + for line in testDict["__raw_lines__"]: + print(" |", line) + print("Solution:") + for line in solutionDict["__raw_lines__"]: + print(" |", line) + + +def runTest(testName, moduleDict, printTestCase=False, display=None): + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + testDict = testParser.TestParser(testName + ".test").parse() + solutionDict = testParser.TestParser(testName + ".solution").parse() + test_out_file = os.path.join('%s.test_output' % testName) + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + + questionClass = getattr(testClasses, 'Question') + question = questionClass({'max_points': 0}, display) + testCase = testClass(question, testDict) + + if printTestCase: + printTest(testDict, solutionDict) + + # This is a fragile hack to create a stub grades object + grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)]) + testCase.execute(grades, moduleDict, solutionDict) + + +# returns all the tests you need to run in order to run question +def getDepends(testParser, testRoot, question): + allDeps = [question] + questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse() + if 'depends' in questionDict: + depends = questionDict['depends'].split() + for d in depends: + # run dependencies first + allDeps = getDepends(testParser, testRoot, d) + allDeps + return allDeps + +# get list of questions to grade +def getTestSubdirs(testParser, testRoot, questionToGrade): + problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse() + if questionToGrade != None: + questions = getDepends(testParser, testRoot, questionToGrade) + if len(questions) > 1: + print('Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions)) + return questions + if 'order' in problemDict: + return problemDict['order'].split() + return sorted(os.listdir(testRoot)) + + +# evaluate student code +def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, + edxOutput=False, muteOutput=False, gsOutput=False, + printTestCase=False, questionToGrade=None, display=None): + # imports of testbench code. note that the testClasses import must follow + # the import of student code due to dependencies + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + questions = [] + questionDicts = {} + test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade) + for q in test_subdirs: + subdir_path = os.path.join(testRoot, q) + if not os.path.isdir(subdir_path) or q[0] == '.': + continue + + # create a question object + questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse() + questionClass = getattr(testClasses, questionDict['class']) + question = questionClass(questionDict, display) + questionDicts[q] = questionDict + + # load test cases into question + tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path)) + tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests) + for t in sorted(tests): + test_file = os.path.join(subdir_path, '%s.test' % t) + solution_file = os.path.join(subdir_path, '%s.solution' % t) + test_out_file = os.path.join(subdir_path, '%s.test_output' % t) + testDict = testParser.TestParser(test_file).parse() + if testDict.get("disabled", "false").lower() == "true": + continue + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + testCase = testClass(question, testDict) + def makefun(testCase, solution_file): + if generateSolutions: + # write solution file to disk + return lambda grades: testCase.writeSolution(moduleDict, solution_file) + else: + # read in solution dictionary and pass as an argument + testDict = testParser.TestParser(test_file).parse() + solutionDict = testParser.TestParser(solution_file).parse() + if printTestCase: + return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict) + else: + return lambda grades: testCase.execute(grades, moduleDict, solutionDict) + question.addTestCase(testCase, makefun(testCase, solution_file)) + + # Note extra function is necessary for scoping reasons + def makefun(question): + return lambda grades: question.execute(grades) + setattr(sys.modules[__name__], q, makefun(question)) + questions.append((q, question.getMaxPoints())) + + grades = grading.Grades(projectParams.PROJECT_NAME, questions, + gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput) + if questionToGrade == None: + for q in questionDicts: + for prereq in questionDicts[q].get('depends', '').split(): + grades.addPrereq(q, prereq) + + grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC) + return grades.points + + + +def getDisplay(graphicsByDefault, options=None): + graphics = graphicsByDefault + if options is not None and options.noGraphics: + graphics = False + if graphics: + try: + import graphicsDisplay + return graphicsDisplay.PacmanGraphics(1, frameTime=.05) + except ImportError: + pass + import textDisplay + return textDisplay.NullGraphics() + + + + +if __name__ == '__main__': + options = readCommand(sys.argv) + if options.generateSolutions: + confirmGenerate() + codePaths = options.studentCode.split(',') + # moduleCodeDict = {} + # for cp in codePaths: + # moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot) + # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot) + # moduleDict = loadModuleDict(moduleCodeDict) + + moduleDict = {} + for cp in codePaths: + moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp)) + moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1) + moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode)) + + + if options.runTest != None: + runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options)) + else: + evaluate(options.generateSolutions, options.testRoot, moduleDict, + gsOutput=options.gsOutput, + edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase, + questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options)) diff --git a/school/cpsc481/project1/src/commands.txt b/school/cpsc481/project1/src/commands.txt new file mode 100644 index 0000000..21683ac --- /dev/null +++ b/school/cpsc481/project1/src/commands.txt @@ -0,0 +1,21 @@ +python pacman.py +python pacman.py --layout testMaze --pacman GoWestAgent +python pacman.py --layout tinyMaze --pacman GoWestAgent +python pacman.py -h +python pacman.py -l tinyMaze -p SearchAgent -a fn=tinyMazeSearch +python pacman.py -l tinyMaze -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent -a fn=bfs +python pacman.py -l bigMaze -p SearchAgent -a fn=bfs -z .5 +python eightpuzzle.py +python pacman.py -l mediumMaze -p SearchAgent -a fn=ucs +python pacman.py -l mediumDottedMaze -p StayEastSearchAgent +python pacman.py -l mediumScaryMaze -p StayWestSearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent -a fn=astar,heuristic=manhattanHeuristic +python pacman.py -l tinyCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5 +python pacman.py -l testSearch -p AStarFoodSearchAgent +python pacman.py -l trickySearch -p AStarFoodSearchAgent +python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5 diff --git a/school/cpsc481/project1/src/eightpuzzle.py b/school/cpsc481/project1/src/eightpuzzle.py new file mode 100644 index 0000000..bb8ea6f --- /dev/null +++ b/school/cpsc481/project1/src/eightpuzzle.py @@ -0,0 +1,281 @@ +# eightpuzzle.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import search +import random + +# Module Classes + +class EightPuzzleState: + """ + The Eight Puzzle is described in the course textbook on + page 64. + + This class defines the mechanics of the puzzle itself. The + task of recasting this puzzle as a search problem is left to + the EightPuzzleSearchProblem class. + """ + + def __init__( self, numbers ): + """ + Constructs a new eight puzzle from an ordering of numbers. + + numbers: a list of integers from 0 to 8 representing an + instance of the eight puzzle. 0 represents the blank + space. Thus, the list + + [1, 0, 2, 3, 4, 5, 6, 7, 8] + + represents the eight puzzle: + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------ + + The configuration of the puzzle is stored in a 2-dimensional + list (a list of lists) 'cells'. + """ + self.cells = [] + numbers = numbers[:] # Make a copy so as not to cause side-effects. + numbers.reverse() + for row in range( 3 ): + self.cells.append( [] ) + for col in range( 3 ): + self.cells[row].append( numbers.pop() ) + if self.cells[row][col] == 0: + self.blankLocation = row, col + + def isGoal( self ): + """ + Checks to see if the puzzle is in its goal state. + + ------------- + | | 1 | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).isGoal() + True + + >>> EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).isGoal() + False + """ + current = 0 + for row in range( 3 ): + for col in range( 3 ): + if current != self.cells[row][col]: + return False + current += 1 + return True + + def legalMoves( self ): + """ + Returns a list of legal moves from the current state. + + Moves consist of moving the blank space up, down, left or right. + These are encoded as 'up', 'down', 'left' and 'right' respectively. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).legalMoves() + ['down', 'right'] + """ + moves = [] + row, col = self.blankLocation + if(row != 0): + moves.append('up') + if(row != 2): + moves.append('down') + if(col != 0): + moves.append('left') + if(col != 2): + moves.append('right') + return moves + + def result(self, move): + """ + Returns a new eightPuzzle with the current state and blankLocation + updated based on the provided move. + + The move should be a string drawn from a list returned by legalMoves. + Illegal moves will raise an exception, which may be an array bounds + exception. + + NOTE: This function *does not* change the current object. Instead, + it returns a new object. + """ + row, col = self.blankLocation + if(move == 'up'): + newrow = row - 1 + newcol = col + elif(move == 'down'): + newrow = row + 1 + newcol = col + elif(move == 'left'): + newrow = row + newcol = col - 1 + elif(move == 'right'): + newrow = row + newcol = col + 1 + else: + raise "Illegal Move" + + # Create a copy of the current eightPuzzle + newPuzzle = EightPuzzleState([0, 0, 0, 0, 0, 0, 0, 0, 0]) + newPuzzle.cells = [values[:] for values in self.cells] + # And update it to reflect the move + newPuzzle.cells[row][col] = self.cells[newrow][newcol] + newPuzzle.cells[newrow][newcol] = self.cells[row][col] + newPuzzle.blankLocation = newrow, newcol + + return newPuzzle + + # Utilities for comparison and display + def __eq__(self, other): + """ + Overloads '==' such that two eightPuzzles with the same configuration + are equal. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]) == \ + EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).result('left') + True + """ + for row in range( 3 ): + if self.cells[row] != other.cells[row]: + return False + return True + + def __hash__(self): + return hash(str(self.cells)) + + def __getAsciiString(self): + """ + Returns a display string for the maze + """ + lines = [] + horizontalLine = ('-' * (13)) + lines.append(horizontalLine) + for row in self.cells: + rowLine = '|' + for col in row: + if col == 0: + col = ' ' + rowLine = rowLine + ' ' + col.__str__() + ' |' + lines.append(rowLine) + lines.append(horizontalLine) + return '\n'.join(lines) + + def __str__(self): + return self.__getAsciiString() + +# TODO: Implement The methods in this class + +class EightPuzzleSearchProblem(search.SearchProblem): + """ + Implementation of a SearchProblem for the Eight Puzzle domain + + Each state is represented by an instance of an eightPuzzle. + """ + def __init__(self,puzzle): + "Creates a new EightPuzzleSearchProblem which stores search information." + self.puzzle = puzzle + + def getStartState(self): + return puzzle + + def isGoalState(self,state): + return state.isGoal() + + def getSuccessors(self,state): + """ + Returns list of (successor, action, stepCost) pairs where + each succesor is either left, right, up, or down + from the original state and the cost is 1.0 for each + """ + succ = [] + for a in state.legalMoves(): + succ.append((state.result(a), a, 1)) + return succ + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. The sequence must + be composed of legal moves + """ + return len(actions) + +EIGHT_PUZZLE_DATA = [[1, 0, 2, 3, 4, 5, 6, 7, 8], + [1, 7, 8, 2, 3, 4, 5, 6, 0], + [4, 3, 2, 7, 0, 5, 1, 6, 8], + [5, 1, 3, 4, 0, 2, 6, 7, 8], + [1, 2, 5, 7, 6, 8, 0, 4, 3], + [0, 3, 1, 6, 8, 2, 7, 5, 4]] + +def loadEightPuzzle(puzzleNumber): + """ + puzzleNumber: The number of the eight puzzle to load. + + Returns an eight puzzle object generated from one of the + provided puzzles in EIGHT_PUZZLE_DATA. + + puzzleNumber can range from 0 to 5. + + >>> print(loadEightPuzzle(0)) + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + """ + return EightPuzzleState(EIGHT_PUZZLE_DATA[puzzleNumber]) + +def createRandomEightPuzzle(moves=100): + """ + moves: number of random moves to apply + + Creates a random eight puzzle by applying + a series of 'moves' random moves to a solved + puzzle. + """ + puzzle = EightPuzzleState([0,1,2,3,4,5,6,7,8]) + for i in range(moves): + # Execute a random legal move + puzzle = puzzle.result(random.sample(puzzle.legalMoves(), 1)[0]) + return puzzle + +if __name__ == '__main__': + puzzle = createRandomEightPuzzle(25) + print('A random puzzle:') + print(puzzle) + + problem = EightPuzzleSearchProblem(puzzle) + path = search.breadthFirstSearch(problem) + print('BFS found a path of %d moves: %s' % (len(path), str(path))) + curr = puzzle + i = 1 + for a in path: + curr = curr.result(a) + print('After %d move%s: %s' % (i, ("", "s")[i>1], a)) + print(curr) + + input("Press return for the next state...") # wait for key stroke + i += 1 diff --git a/school/cpsc481/project1/src/game.py b/school/cpsc481/project1/src/game.py new file mode 100644 index 0000000..3950f27 --- /dev/null +++ b/school/cpsc481/project1/src/game.py @@ -0,0 +1,729 @@ +# game.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# game.py +# ------- +# Licensing Information: Please do not distribute or publish solutions to this +# project. You are free to use and extend these projects for educational +# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by +# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html + +from util import * +import time, os +import traceback +import sys + +####################### +# Parts worth reading # +####################### + +class Agent: + """ + An agent must define a getAction method, but may also define the + following methods which will be called if they exist: + + def registerInitialState(self, state): # inspects the starting state + """ + def __init__(self, index=0): + self.index = index + + def getAction(self, state): + """ + The Agent will receive a GameState (from either {pacman, capture, sonar}.py) and + must return an action from Directions.{North, South, East, West, Stop} + """ + raiseNotDefined() + +class Directions: + NORTH = 'North' + SOUTH = 'South' + EAST = 'East' + WEST = 'West' + STOP = 'Stop' + + LEFT = {NORTH: WEST, + SOUTH: EAST, + EAST: NORTH, + WEST: SOUTH, + STOP: STOP} + + RIGHT = dict([(y,x) for x, y in LEFT.items()]) + + REVERSE = {NORTH: SOUTH, + SOUTH: NORTH, + EAST: WEST, + WEST: EAST, + STOP: STOP} + +class Configuration: + """ + A Configuration holds the (x,y) coordinate of a character, along with its + traveling direction. + + The convention for positions, like a graph, is that (0,0) is the lower left corner, x increases + horizontally and y increases vertically. Therefore, north is the direction of increasing y, or (0,1). + """ + + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def getPosition(self): + return (self.pos) + + def getDirection(self): + return self.direction + + def isInteger(self): + x,y = self.pos + return x == int(x) and y == int(y) + + def __eq__(self, other): + if other == None: return False + return (self.pos == other.pos and self.direction == other.direction) + + def __hash__(self): + x = hash(self.pos) + y = hash(self.direction) + return hash(x + 13 * y) + + def __str__(self): + return "(x,y)="+str(self.pos)+", "+str(self.direction) + + def generateSuccessor(self, vector): + """ + Generates a new configuration reached by translating the current + configuration by the action vector. This is a low-level call and does + not attempt to respect the legality of the movement. + + Actions are movement vectors. + """ + x, y= self.pos + dx, dy = vector + direction = Actions.vectorToDirection(vector) + if direction == Directions.STOP: + direction = self.direction # There is no stop direction + return Configuration((x + dx, y+dy), direction) + +class AgentState: + """ + AgentStates hold the state of an agent (configuration, speed, scared, etc). + """ + + def __init__( self, startConfiguration, isPacman ): + self.start = startConfiguration + self.configuration = startConfiguration + self.isPacman = isPacman + self.scaredTimer = 0 + self.numCarrying = 0 + self.numReturned = 0 + + def __str__( self ): + if self.isPacman: + return "Pacman: " + str( self.configuration ) + else: + return "Ghost: " + str( self.configuration ) + + def __eq__( self, other ): + if other == None: + return False + return self.configuration == other.configuration and self.scaredTimer == other.scaredTimer + + def __hash__(self): + return hash(hash(self.configuration) + 13 * hash(self.scaredTimer)) + + def copy( self ): + state = AgentState( self.start, self.isPacman ) + state.configuration = self.configuration + state.scaredTimer = self.scaredTimer + state.numCarrying = self.numCarrying + state.numReturned = self.numReturned + return state + + def getPosition(self): + if self.configuration == None: return None + return self.configuration.getPosition() + + def getDirection(self): + return self.configuration.getDirection() + +class Grid: + """ + A 2-dimensional array of objects backed by a list of lists. Data is accessed + via grid[x][y] where (x,y) are positions on a Pacman map with x horizontal, + y vertical and the origin (0,0) in the bottom left corner. + + The __str__ method constructs an output that is oriented like a pacman board. + """ + def __init__(self, width, height, initialValue=False, bitRepresentation=None): + if initialValue not in [False, True]: raise Exception('Grids can only contain booleans') + self.CELLS_PER_INT = 30 + + self.width = width + self.height = height + self.data = [[initialValue for y in range(height)] for x in range(width)] + if bitRepresentation: + self._unpackBits(bitRepresentation) + + def __getitem__(self, i): + return self.data[i] + + def __setitem__(self, key, item): + self.data[key] = item + + def __str__(self): + out = [[str(self.data[x][y])[0] for x in range(self.width)] for y in range(self.height)] + out.reverse() + return '\n'.join([''.join(x) for x in out]) + + def __eq__(self, other): + if other == None: return False + return self.data == other.data + + def __hash__(self): + # return hash(str(self)) + base = 1 + h = 0 + for l in self.data: + for i in l: + if i: + h += base + base *= 2 + return hash(h) + + def copy(self): + g = Grid(self.width, self.height) + g.data = [x[:] for x in self.data] + return g + + def deepCopy(self): + return self.copy() + + def shallowCopy(self): + g = Grid(self.width, self.height) + g.data = self.data + return g + + def count(self, item =True ): + return sum([x.count(item) for x in self.data]) + + def asList(self, key = True): + list = [] + for x in range(self.width): + for y in range(self.height): + if self[x][y] == key: list.append( (x,y) ) + return list + + def packBits(self): + """ + Returns an efficient int list representation + + (width, height, bitPackedInts...) + """ + bits = [self.width, self.height] + currentInt = 0 + for i in range(self.height * self.width): + bit = self.CELLS_PER_INT - (i % self.CELLS_PER_INT) - 1 + x, y = self._cellIndexToPosition(i) + if self[x][y]: + currentInt += 2 ** bit + if (i + 1) % self.CELLS_PER_INT == 0: + bits.append(currentInt) + currentInt = 0 + bits.append(currentInt) + return tuple(bits) + + def _cellIndexToPosition(self, index): + x = index // self.height + y = index % self.height + return x, y + + def _unpackBits(self, bits): + """ + Fills in data from a bit-level representation + """ + cell = 0 + for packed in bits: + for bit in self._unpackInt(packed, self.CELLS_PER_INT): + if cell == self.width * self.height: break + x, y = self._cellIndexToPosition(cell) + self[x][y] = bit + cell += 1 + + def _unpackInt(self, packed, size): + bools = [] + if packed < 0: raise ValueError("must be a positive integer") + for i in range(size): + n = 2 ** (self.CELLS_PER_INT - i - 1) + if packed >= n: + bools.append(True) + packed -= n + else: + bools.append(False) + return bools + +def reconstituteGrid(bitRep): + if type(bitRep) is not type((1,2)): + return bitRep + width, height = bitRep[:2] + return Grid(width, height, bitRepresentation= bitRep[2:]) + +#################################### +# Parts you shouldn't have to read # +#################################### + +class Actions: + """ + A collection of static methods for manipulating move actions. + """ + # Directions + _directions = {Directions.NORTH: (0, 1), + Directions.SOUTH: (0, -1), + Directions.EAST: (1, 0), + Directions.WEST: (-1, 0), + Directions.STOP: (0, 0)} + + _directionsAsList = _directions.items() + + TOLERANCE = .001 + + def reverseDirection(action): + if action == Directions.NORTH: + return Directions.SOUTH + if action == Directions.SOUTH: + return Directions.NORTH + if action == Directions.EAST: + return Directions.WEST + if action == Directions.WEST: + return Directions.EAST + return action + reverseDirection = staticmethod(reverseDirection) + + def vectorToDirection(vector): + dx, dy = vector + if dy > 0: + return Directions.NORTH + if dy < 0: + return Directions.SOUTH + if dx < 0: + return Directions.WEST + if dx > 0: + return Directions.EAST + return Directions.STOP + vectorToDirection = staticmethod(vectorToDirection) + + def directionToVector(direction, speed = 1.0): + dx, dy = Actions._directions[direction] + return (dx * speed, dy * speed) + directionToVector = staticmethod(directionToVector) + + def getPossibleActions(config, walls): + possible = [] + x, y = config.pos + x_int, y_int = int(x + 0.5), int(y + 0.5) + + # In between grid points, all agents must continue straight + if (abs(x - x_int) + abs(y - y_int) > Actions.TOLERANCE): + return [config.getDirection()] + + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_y = y_int + dy + next_x = x_int + dx + if not walls[next_x][next_y]: possible.append(dir) + + return possible + + getPossibleActions = staticmethod(getPossibleActions) + + def getLegalNeighbors(position, walls): + x,y = position + x_int, y_int = int(x + 0.5), int(y + 0.5) + neighbors = [] + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_x = x_int + dx + if next_x < 0 or next_x == walls.width: continue + next_y = y_int + dy + if next_y < 0 or next_y == walls.height: continue + if not walls[next_x][next_y]: neighbors.append((next_x, next_y)) + return neighbors + getLegalNeighbors = staticmethod(getLegalNeighbors) + + def getSuccessor(position, action): + dx, dy = Actions.directionToVector(action) + x, y = position + return (x + dx, y + dy) + getSuccessor = staticmethod(getSuccessor) + +class GameStateData: + """ + + """ + def __init__( self, prevState = None ): + """ + Generates a new data packet by copying information from its predecessor. + """ + if prevState != None: + self.food = prevState.food.shallowCopy() + self.capsules = prevState.capsules[:] + self.agentStates = self.copyAgentStates( prevState.agentStates ) + self.layout = prevState.layout + self._eaten = prevState._eaten + self.score = prevState.score + + self._foodEaten = None + self._foodAdded = None + self._capsuleEaten = None + self._agentMoved = None + self._lose = False + self._win = False + self.scoreChange = 0 + + def deepCopy( self ): + state = GameStateData( self ) + state.food = self.food.deepCopy() + state.layout = self.layout.deepCopy() + state._agentMoved = self._agentMoved + state._foodEaten = self._foodEaten + state._foodAdded = self._foodAdded + state._capsuleEaten = self._capsuleEaten + return state + + def copyAgentStates( self, agentStates ): + copiedStates = [] + for agentState in agentStates: + copiedStates.append( agentState.copy() ) + return copiedStates + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + if other == None: return False + # TODO Check for type of other + if not self.agentStates == other.agentStates: return False + if not self.food == other.food: return False + if not self.capsules == other.capsules: return False + if not self.score == other.score: return False + return True + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + for i, state in enumerate( self.agentStates ): + try: + int(hash(state)) + except TypeError as e: + print(e) + #hash(state) + return int((hash(tuple(self.agentStates)) + 13*hash(self.food) + 113* hash(tuple(self.capsules)) + 7 * hash(self.score)) % 1048575 ) + + def __str__( self ): + width, height = self.layout.width, self.layout.height + map = Grid(width, height) + if type(self.food) == type((1,2)): + self.food = reconstituteGrid(self.food) + for x in range(width): + for y in range(height): + food, walls = self.food, self.layout.walls + map[x][y] = self._foodWallStr(food[x][y], walls[x][y]) + + for agentState in self.agentStates: + if agentState == None: continue + if agentState.configuration == None: continue + x,y = [int( i ) for i in nearestPoint( agentState.configuration.pos )] + agent_dir = agentState.configuration.direction + if agentState.isPacman: + map[x][y] = self._pacStr( agent_dir ) + else: + map[x][y] = self._ghostStr( agent_dir ) + + for x, y in self.capsules: + map[x][y] = 'o' + + return str(map) + ("\nScore: %d\n" % self.score) + + def _foodWallStr( self, hasFood, hasWall ): + if hasFood: + return '.' + elif hasWall: + return '%' + else: + return ' ' + + def _pacStr( self, dir ): + if dir == Directions.NORTH: + return 'v' + if dir == Directions.SOUTH: + return '^' + if dir == Directions.WEST: + return '>' + return '<' + + def _ghostStr( self, dir ): + return 'G' + if dir == Directions.NORTH: + return 'M' + if dir == Directions.SOUTH: + return 'W' + if dir == Directions.WEST: + return '3' + return 'E' + + def initialize( self, layout, numGhostAgents ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.food = layout.food.copy() + #self.capsules = [] + self.capsules = layout.capsules[:] + self.layout = layout + self.score = 0 + self.scoreChange = 0 + + self.agentStates = [] + numGhosts = 0 + for isPacman, pos in layout.agentPositions: + if not isPacman: + if numGhosts == numGhostAgents: continue # Max ghosts reached already + else: numGhosts += 1 + self.agentStates.append( AgentState( Configuration( pos, Directions.STOP), isPacman) ) + self._eaten = [False for a in self.agentStates] + +try: + import boinc + _BOINC_ENABLED = True +except: + _BOINC_ENABLED = False + +class Game: + """ + The Game manages the control flow, soliciting actions from agents. + """ + + def __init__( self, agents, display, rules, startingIndex=0, muteAgents=False, catchExceptions=False ): + self.agentCrashed = False + self.agents = agents + self.display = display + self.rules = rules + self.startingIndex = startingIndex + self.gameOver = False + self.muteAgents = muteAgents + self.catchExceptions = catchExceptions + self.moveHistory = [] + self.totalAgentTimes = [0 for agent in agents] + self.totalAgentTimeWarnings = [0 for agent in agents] + self.agentTimeout = False + import io + self.agentOutput = [io.StringIO() for agent in agents] + + def getProgress(self): + if self.gameOver: + return 1.0 + else: + return self.rules.getProgress(self) + + def _agentCrash( self, agentIndex, quiet=False): + "Helper method for handling agent crashes" + if not quiet: traceback.print_exc() + self.gameOver = True + self.agentCrashed = True + self.rules.agentCrash(self, agentIndex) + + OLD_STDOUT = None + OLD_STDERR = None + + def mute(self, agentIndex): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + import io + OLD_STDOUT = sys.stdout + OLD_STDERR = sys.stderr + sys.stdout = self.agentOutput[agentIndex] + sys.stderr = self.agentOutput[agentIndex] + + def unmute(self): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + # Revert stdout/stderr to originals + sys.stdout = OLD_STDOUT + sys.stderr = OLD_STDERR + + + def run( self ): + """ + Main control loop for game play. + """ + self.display.initialize(self.state.data) + self.numMoves = 0 + + ###self.display.initialize(self.state.makeObservation(1).data) + # inform learning agents of the game start + for i in range(len(self.agents)): + agent = self.agents[i] + if not agent: + self.mute(i) + # this is a null agent, meaning it failed to load + # the other team wins + print("Agent %d failed to load" % i, file=sys.stderr) + self.unmute() + self._agentCrash(i, quiet=True) + return + if ("registerInitialState" in dir(agent)): + self.mute(i) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.registerInitialState, int(self.rules.getMaxStartupTime(i))) + try: + start_time = time.time() + timed_func(self.state.deepCopy()) + time_taken = time.time() - start_time + self.totalAgentTimes[i] += time_taken + except TimeoutFunctionException: + print("Agent %d ran out of time on startup!" % i, file=sys.stderr) + self.unmute() + self.agentTimeout = True + self._agentCrash(i, quiet=True) + return + except Exception as data: + self._agentCrash(i, quiet=False) + self.unmute() + return + else: + agent.registerInitialState(self.state.deepCopy()) + ## TODO: could this exceed the total time + self.unmute() + + agentIndex = self.startingIndex + numAgents = len( self.agents ) + + while not self.gameOver: + # Fetch the next agent + agent = self.agents[agentIndex] + move_time = 0 + skip_action = False + # Generate an observation of the state + if 'observationFunction' in dir( agent ): + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.observationFunction, int(self.rules.getMoveTimeout(agentIndex))) + try: + start_time = time.time() + observation = timed_func(self.state.deepCopy()) + except TimeoutFunctionException: + skip_action = True + move_time += time.time() - start_time + self.unmute() + except Exception as data: + self._agentCrash(agentIndex, quiet=False) + self.unmute() + return + else: + observation = agent.observationFunction(self.state.deepCopy()) + self.unmute() + else: + observation = self.state.deepCopy() + + # Solicit an action + action = None + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.getAction, int(self.rules.getMoveTimeout(agentIndex)) - int(move_time)) + try: + start_time = time.time() + if skip_action: + raise TimeoutFunctionException() + action = timed_func( observation ) + except TimeoutFunctionException: + print("Agent %d timed out on a single move!" % agentIndex, file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + move_time += time.time() - start_time + + if move_time > self.rules.getMoveWarningTime(agentIndex): + self.totalAgentTimeWarnings[agentIndex] += 1 + print("Agent %d took too long to make a move! This is warning %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + if self.totalAgentTimeWarnings[agentIndex] > self.rules.getMaxTimeWarnings(agentIndex): + print("Agent %d exceeded the maximum number of warnings: %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + self.totalAgentTimes[agentIndex] += move_time + #print("Agent: %d, time: %f, total: %f" % (agentIndex, move_time, self.totalAgentTimes[agentIndex])) + if self.totalAgentTimes[agentIndex] > self.rules.getMaxTotalTime(agentIndex): + print("Agent %d ran out of time! (time: %1.2f)" % (agentIndex, self.totalAgentTimes[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + self.unmute() + except Exception as data: + self._agentCrash(agentIndex) + self.unmute() + return + else: + action = agent.getAction(observation) + self.unmute() + + # Execute the action + self.moveHistory.append( (agentIndex, action) ) + if self.catchExceptions: + try: + self.state = self.state.generateSuccessor( agentIndex, action ) + except Exception as data: + self.mute(agentIndex) + self._agentCrash(agentIndex) + self.unmute() + return + else: + self.state = self.state.generateSuccessor( agentIndex, action ) + + # Change the display + self.display.update( self.state.data ) + ###idx = agentIndex - agentIndex % 2 + 1 + ###self.display.update( self.state.makeObservation(idx).data ) + + # Allow for game specific conditions (winning, losing, etc.) + self.rules.process(self.state, self) + # Track progress + if agentIndex == numAgents + 1: self.numMoves += 1 + # Next agent + agentIndex = ( agentIndex + 1 ) % numAgents + + if _BOINC_ENABLED: + boinc.set_fraction_done(self.getProgress()) + + # inform a learning agent of the game result + for agentIndex, agent in enumerate(self.agents): + if "final" in dir( agent ) : + try: + self.mute(agentIndex) + agent.final( self.state ) + self.unmute() + except Exception as data: + if not self.catchExceptions: raise data + self._agentCrash(agentIndex) + self.unmute() + return + self.display.finish() diff --git a/school/cpsc481/project1/src/ghostAgents.py b/school/cpsc481/project1/src/ghostAgents.py new file mode 100644 index 0000000..c3afe1f --- /dev/null +++ b/school/cpsc481/project1/src/ghostAgents.py @@ -0,0 +1,81 @@ +# ghostAgents.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Actions +from game import Directions +import random +from util import manhattanDistance +import util + +class GhostAgent( Agent ): + def __init__( self, index ): + self.index = index + + def getAction( self, state ): + dist = self.getDistribution(state) + if len(dist) == 0: + return Directions.STOP + else: + return util.chooseFromDistribution( dist ) + + def getDistribution(self, state): + "Returns a Counter encoding a distribution over actions from the provided state." + util.raiseNotDefined() + +class RandomGhost( GhostAgent ): + "A ghost that chooses a legal action uniformly at random." + def getDistribution( self, state ): + dist = util.Counter() + for a in state.getLegalActions( self.index ): dist[a] = 1.0 + dist.normalize() + return dist + +class DirectionalGhost( GhostAgent ): + "A ghost that prefers to rush Pacman, or flee when scared." + def __init__( self, index, prob_attack=0.8, prob_scaredFlee=0.8 ): + self.index = index + self.prob_attack = prob_attack + self.prob_scaredFlee = prob_scaredFlee + + def getDistribution( self, state ): + # Read variables from state + ghostState = state.getGhostState( self.index ) + legalActions = state.getLegalActions( self.index ) + pos = state.getGhostPosition( self.index ) + isScared = ghostState.scaredTimer > 0 + + speed = 1 + if isScared: speed = 0.5 + + actionVectors = [Actions.directionToVector( a, speed ) for a in legalActions] + newPositions = [( pos[0]+a[0], pos[1]+a[1] ) for a in actionVectors] + pacmanPosition = state.getPacmanPosition() + + # Select best actions given the state + distancesToPacman = [manhattanDistance( pos, pacmanPosition ) for pos in newPositions] + if isScared: + bestScore = max( distancesToPacman ) + bestProb = self.prob_scaredFlee + else: + bestScore = min( distancesToPacman ) + bestProb = self.prob_attack + bestActions = [action for action, distance in zip( legalActions, distancesToPacman ) if distance == bestScore] + + # Construct distribution + dist = util.Counter() + for a in bestActions: dist[a] = bestProb / len(bestActions) + for a in legalActions: dist[a] += ( 1-bestProb ) / len(legalActions) + dist.normalize() + return dist diff --git a/school/cpsc481/project1/src/grading.py b/school/cpsc481/project1/src/grading.py new file mode 100644 index 0000000..6384a56 --- /dev/null +++ b/school/cpsc481/project1/src/grading.py @@ -0,0 +1,323 @@ +# grading.py +# ---------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +"Common code for autograders" + +import cgi +import time +import sys +import json +import traceback +import pdb +from collections import defaultdict +import util + +class Grades: + "A data structure for project grades, along with formatting code to display them" + def __init__(self, projectName, questionsAndMaxesList, + gsOutput=False, edxOutput=False, muteOutput=False): + """ + Defines the grading scheme for a project + projectName: project name + questionsAndMaxesDict: a list of (question name, max points per question) + """ + self.questions = [el[0] for el in questionsAndMaxesList] + self.maxes = dict(questionsAndMaxesList) + self.points = Counter() + self.messages = dict([(q, []) for q in self.questions]) + self.project = projectName + self.start = time.localtime()[1:6] + self.sane = True # Sanity checks + self.currentQuestion = None # Which question we're grading + self.edxOutput = edxOutput + self.gsOutput = gsOutput # GradeScope output + self.mute = muteOutput + self.prereqs = defaultdict(set) + + #print('Autograder transcript for %s' % self.project) + print('Starting on %d-%d at %d:%02d:%02d' % self.start) + + def addPrereq(self, question, prereq): + self.prereqs[question].add(prereq) + + def grade(self, gradingModule, exceptionMap = {}, bonusPic = False): + """ + Grades each question + gradingModule: the module with all the grading functions (pass in with sys.modules[__name__]) + """ + + completedQuestions = set([]) + for q in self.questions: + print('\nQuestion %s' % q) + print('=' * (9 + len(q))) + print + self.currentQuestion = q + + incompleted = self.prereqs[q].difference(completedQuestions) + if len(incompleted) > 0: + prereq = incompleted.pop() + print( +"""*** NOTE: Make sure to complete Question %s before working on Question %s, +*** because Question %s builds upon your answer for Question %s. +""" % (prereq, q, q, prereq)) + continue + + if self.mute: util.mutePrint() + try: + util.TimeoutFunction(getattr(gradingModule, q),1800)(self) # Call the question's function + #TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function + except Exception as inst: + self.addExceptionMessage(q, inst, traceback) + self.addErrorHints(exceptionMap, inst, q[1]) + except: + self.fail('FAIL: Terminated with a string exception.') + finally: + if self.mute: util.unmutePrint() + + if self.points[q] >= self.maxes[q]: + completedQuestions.add(q) + + print('\n### Question %s: %d/%d ###\n' % (q, self.points[q], self.maxes[q])) + + + print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6]) + print("\nProvisional grades\n==================") + + for q in self.questions: + print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q])) + print('------------------') + print('Total: %d/%d' % (self.points.totalCount(), sum(self.maxes.values()))) + if bonusPic and self.points.totalCount() == 25: + print(""" + + ALL HAIL GRANDPAC. + LONG LIVE THE GHOSTBUSTING KING. + + --- ---- --- + | \ / + \ / | + | + \--/ \--/ + | + | + + | + | + + + | + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@ + V @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + /\ @@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@ + /\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + +""") + print(""" +Your grades are NOT yet registered. To register your grades, make sure +to follow your instructor's guidelines to receive credit on your project. +""") + + if self.edxOutput: + self.produceOutput() + if self.gsOutput: + self.produceGradeScopeOutput() + + def addExceptionMessage(self, q, inst, traceback): + """ + Method to format the exception message, this is more complicated because + we need to cgi.escape the traceback but wrap the exception in a
 tag
+    """
+    self.fail('FAIL: Exception raised: %s' % inst)
+    self.addMessage('')
+    for line in traceback.format_exc().split('\n'):
+        self.addMessage(line)
+
+  def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+    typeOf = str(type(errorInstance))
+    questionName = 'q' + questionNum
+    errorHint = ''
+
+    # question specific error hints
+    if exceptionMap.get(questionName):
+      questionMap = exceptionMap.get(questionName)
+      if (questionMap.get(typeOf)):
+        errorHint = questionMap.get(typeOf)
+    # fall back to general error messages if a question specific
+    # one does not exist
+    if (exceptionMap.get(typeOf)):
+      errorHint = exceptionMap.get(typeOf)
+
+    # dont include the HTML if we have no error hint
+    if not errorHint:
+      return ''
+
+    for line in errorHint.split('\n'):
+      self.addMessage(line)
+
+  def produceGradeScopeOutput(self):
+    out_dct = {}
+
+    # total of entire submission
+    total_possible = sum(self.maxes.values())
+    total_score = sum(self.points.values())
+    out_dct['score'] = total_score
+    out_dct['max_score'] = total_possible
+    out_dct['output'] = "Total score (%d / %d)" % (total_score, total_possible)
+
+    # individual tests
+    tests_out = []
+    for name in self.questions:
+      test_out = {}
+      # test name
+      test_out['name'] = name
+      # test score
+      test_out['score'] = self.points[name]
+      test_out['max_score'] = self.maxes[name]
+      # others
+      is_correct = self.points[name] >= self.maxes[name]
+      test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+          num=(name[1] if len(name) == 2 else name),
+          points=test_out['score'],
+          max=test_out['max_score'],
+          correct=('X' if not is_correct else ''),
+      )
+      test_out['tags'] = []
+      tests_out.append(test_out)
+    out_dct['tests'] = tests_out
+
+    # file output
+    with open('gradescope_response.json', 'w') as outfile:
+        json.dump(out_dct, outfile)
+    return
+
+  def produceOutput(self):
+    edxOutput = open('edx_response.html', 'w')
+    edxOutput.write("
") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '' + if (total_score >= total_possible): + checkOrX = '' + header = """ +

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

+ """.format(total_score = total_score, + total_possible = total_possible, + checkOrX = checkOrX + ) + edxOutput.write(header) + + for q in self.questions: + if len(q) == 2: + name = q[1] + else: + name = q + checkOrX = '' + if (self.points[q] >= self.maxes[q]): + checkOrX = '' + #messages = '\n
\n'.join(self.messages[q]) + messages = "
%s
" % '\n'.join(self.messages[q]) + output = """ +
+
+
+ Question {q} ({points}/{max}) {checkOrX} +
+
+ {messages} +
+
+
+ """.format(q = name, + max = self.maxes[q], + messages = messages, + checkOrX = checkOrX, + points = self.points[q] + ) + # print("*** output for Question %s " % q[1]) + # print(output) + edxOutput.write(output) + edxOutput.write("
") + edxOutput.close() + edxOutput = open('edx_grade', 'w') + edxOutput.write(str(self.points.totalCount())) + edxOutput.close() + + def fail(self, message, raw=False): + "Sets sanity check bit to false and outputs a message" + self.sane = False + self.assignZeroCredit() + self.addMessage(message, raw) + + def assignZeroCredit(self): + self.points[self.currentQuestion] = 0 + + def addPoints(self, amt): + self.points[self.currentQuestion] += amt + + def deductPoints(self, amt): + self.points[self.currentQuestion] -= amt + + def assignFullCredit(self, message="", raw=False): + self.points[self.currentQuestion] = self.maxes[self.currentQuestion] + if message != "": + self.addMessage(message, raw) + + def addMessage(self, message, raw=False): + if not raw: + # We assume raw messages, formatted for HTML, are printed separately + if self.mute: util.unmutePrint() + print('*** ' + message) + if self.mute: util.mutePrint() + message = cgi.escape(message) + self.messages[self.currentQuestion].append(message) + + def addMessageToEmail(self, message): + print("WARNING**** addMessageToEmail is deprecated %s" % message) + for line in message.split('\n'): + pass + #print('%%% ' + line + ' %%%') + #self.messages[self.currentQuestion].append(line) + + + + + +class Counter(dict): + """ + Dict with default 0 + """ + def __getitem__(self, idx): + try: + return dict.__getitem__(self, idx) + except KeyError: + return 0 + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + diff --git a/school/cpsc481/project1/src/graphicsDisplay.py b/school/cpsc481/project1/src/graphicsDisplay.py new file mode 100644 index 0000000..bd53e9a --- /dev/null +++ b/school/cpsc481/project1/src/graphicsDisplay.py @@ -0,0 +1,679 @@ +# graphicsDisplay.py +# ------------------ +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from graphicsUtils import * +import math, time +from game import Directions + +########################### +# GRAPHICS DISPLAY CODE # +########################### + +# Most code by Dan Klein and John Denero written or rewritten for cs188, UC Berkeley. +# Some code from a Pacman implementation by LiveWires, and used / modified with permission. + +DEFAULT_GRID_SIZE = 30.0 +INFO_PANE_HEIGHT = 35 +BACKGROUND_COLOR = formatColor(0,0,0) +WALL_COLOR = formatColor(0.0/255.0, 51.0/255.0, 255.0/255.0) +INFO_PANE_COLOR = formatColor(.4,.4,0) +SCORE_COLOR = formatColor(.9, .9, .9) +PACMAN_OUTLINE_WIDTH = 2 +PACMAN_CAPTURE_OUTLINE_WIDTH = 4 + +GHOST_COLORS = [] +GHOST_COLORS.append(formatColor(.9,0,0)) # Red +GHOST_COLORS.append(formatColor(0,.3,.9)) # Blue +GHOST_COLORS.append(formatColor(.98,.41,.07)) # Orange +GHOST_COLORS.append(formatColor(.1,.75,.7)) # Green +GHOST_COLORS.append(formatColor(1.0,0.6,0.0)) # Yellow +GHOST_COLORS.append(formatColor(.4,0.13,0.91)) # Purple + +TEAM_COLORS = GHOST_COLORS[:2] + +GHOST_SHAPE = [ + ( 0, 0.3 ), + ( 0.25, 0.75 ), + ( 0.5, 0.3 ), + ( 0.75, 0.75 ), + ( 0.75, -0.5 ), + ( 0.5, -0.75 ), + (-0.5, -0.75 ), + (-0.75, -0.5 ), + (-0.75, 0.75 ), + (-0.5, 0.3 ), + (-0.25, 0.75 ) + ] +GHOST_SIZE = 0.65 +SCARED_COLOR = formatColor(1,1,1) + +GHOST_VEC_COLORS = [colorToVector(c) for c in GHOST_COLORS] + +PACMAN_COLOR = formatColor(255.0/255.0,255.0/255.0,61.0/255) +PACMAN_SCALE = 0.5 +#pacman_speed = 0.25 + +# Food +FOOD_COLOR = formatColor(1,1,1) +FOOD_SIZE = 0.1 + +# Laser +LASER_COLOR = formatColor(1,0,0) +LASER_SIZE = 0.02 + +# Capsule graphics +CAPSULE_COLOR = formatColor(1,1,1) +CAPSULE_SIZE = 0.25 + +# Drawing walls +WALL_RADIUS = 0.15 + +class InfoPane: + def __init__(self, layout, gridSize): + self.gridSize = gridSize + self.width = (layout.width) * gridSize + self.base = (layout.height + 1) * gridSize + self.height = INFO_PANE_HEIGHT + self.fontSize = 24 + self.textColor = PACMAN_COLOR + self.drawPane() + + def toScreen(self, pos, y = None): + """ + Translates a point relative from the bottom left of the info pane. + """ + if y == None: + x,y = pos + else: + x = pos + + x = self.gridSize + x # Margin + y = self.base + y + return x,y + + def drawPane(self): + self.scoreText = text( self.toScreen(0, 0 ), self.textColor, "SCORE: 0", "Times", self.fontSize, "bold") + + def initializeGhostDistances(self, distances): + self.ghostDistanceText = [] + + size = 20 + if self.width < 240: + size = 12 + if self.width < 160: + size = 10 + + for i, d in enumerate(distances): + t = text( self.toScreen(self.width//2 + self.width//8 * i, 0), GHOST_COLORS[i+1], d, "Times", size, "bold") + self.ghostDistanceText.append(t) + + def updateScore(self, score): + changeText(self.scoreText, "SCORE: % 4d" % score) + + def setTeam(self, isBlue): + text = "RED TEAM" + if isBlue: text = "BLUE TEAM" + self.teamText = text( self.toScreen(300, 0 ), self.textColor, text, "Times", self.fontSize, "bold") + + def updateGhostDistances(self, distances): + if len(distances) == 0: return + if 'ghostDistanceText' not in dir(self): self.initializeGhostDistances(distances) + else: + for i, d in enumerate(distances): + changeText(self.ghostDistanceText[i], d) + + def drawGhost(self): + pass + + def drawPacman(self): + pass + + def drawWarning(self): + pass + + def clearIcon(self): + pass + + def updateMessage(self, message): + pass + + def clearMessage(self): + pass + + +class PacmanGraphics: + def __init__(self, zoom=1.0, frameTime=0.0, capture=False): + self.have_window = 0 + self.currentGhostImages = {} + self.pacmanImage = None + self.zoom = zoom + self.gridSize = DEFAULT_GRID_SIZE * zoom + self.capture = capture + self.frameTime = frameTime + + def checkNullDisplay(self): + return False + + def initialize(self, state, isBlue = False): + self.isBlue = isBlue + self.startGraphics(state) + + # self.drawDistributions(state) + self.distributionImages = None # Initialized lazily + self.drawStaticObjects(state) + self.drawAgentObjects(state) + + # Information + self.previousState = state + + def startGraphics(self, state): + self.layout = state.layout + layout = self.layout + self.width = layout.width + self.height = layout.height + self.make_window(self.width, self.height) + self.infoPane = InfoPane(layout, self.gridSize) + self.currentState = layout + + def drawDistributions(self, state): + walls = state.layout.walls + dist = [] + for x in range(walls.width): + distx = [] + dist.append(distx) + for y in range(walls.height): + ( screen_x, screen_y ) = self.to_screen( (x, y) ) + block = square( (screen_x, screen_y), + 0.5 * self.gridSize, + color = BACKGROUND_COLOR, + filled = 1, behind=2) + distx.append(block) + self.distributionImages = dist + + def drawStaticObjects(self, state): + layout = self.layout + self.drawWalls(layout.walls) + self.food = self.drawFood(layout.food) + self.capsules = self.drawCapsules(layout.capsules) + refresh() + + def drawAgentObjects(self, state): + self.agentImages = [] # (agentState, image) + for index, agent in enumerate(state.agentStates): + if agent.isPacman: + image = self.drawPacman(agent, index) + self.agentImages.append( (agent, image) ) + else: + image = self.drawGhost(agent, index) + self.agentImages.append( (agent, image) ) + refresh() + + def swapImages(self, agentIndex, newState): + """ + Changes an image from a ghost to a pacman or vis versa (for capture) + """ + prevState, prevImage = self.agentImages[agentIndex] + for item in prevImage: remove_from_screen(item) + if newState.isPacman: + image = self.drawPacman(newState, agentIndex) + self.agentImages[agentIndex] = (newState, image ) + else: + image = self.drawGhost(newState, agentIndex) + self.agentImages[agentIndex] = (newState, image ) + refresh() + + def update(self, newState): + agentIndex = newState._agentMoved + agentState = newState.agentStates[agentIndex] + + if self.agentImages[agentIndex][0].isPacman != agentState.isPacman: self.swapImages(agentIndex, agentState) + prevState, prevImage = self.agentImages[agentIndex] + if agentState.isPacman: + self.animatePacman(agentState, prevState, prevImage) + else: + self.moveGhost(agentState, agentIndex, prevState, prevImage) + self.agentImages[agentIndex] = (agentState, prevImage) + + if newState._foodEaten != None: + self.removeFood(newState._foodEaten, self.food) + if newState._capsuleEaten != None: + self.removeCapsule(newState._capsuleEaten, self.capsules) + self.infoPane.updateScore(newState.score) + if 'ghostDistances' in dir(newState): + self.infoPane.updateGhostDistances(newState.ghostDistances) + + def make_window(self, width, height): + grid_width = (width-1) * self.gridSize + grid_height = (height-1) * self.gridSize + screen_width = 2*self.gridSize + grid_width + screen_height = 2*self.gridSize + grid_height + INFO_PANE_HEIGHT + + begin_graphics(screen_width, + screen_height, + BACKGROUND_COLOR, + "CS188 Pacman") + + def drawPacman(self, pacman, index): + position = self.getPosition(pacman) + screen_point = self.to_screen(position) + endpoints = self.getEndpoints(self.getDirection(pacman)) + + width = PACMAN_OUTLINE_WIDTH + outlineColor = PACMAN_COLOR + fillColor = PACMAN_COLOR + + if self.capture: + outlineColor = TEAM_COLORS[index % 2] + fillColor = GHOST_COLORS[index] + width = PACMAN_CAPTURE_OUTLINE_WIDTH + + return [circle(screen_point, PACMAN_SCALE * self.gridSize, + fillColor = fillColor, outlineColor = outlineColor, + endpoints = endpoints, + width = width)] + + def getEndpoints(self, direction, position=(0,0)): + x, y = position + pos = x - int(x) + y - int(y) + width = 30 + 80 * math.sin(math.pi* pos) + + delta = width / 2 + if (direction == 'West'): + endpoints = (180+delta, 180-delta) + elif (direction == 'North'): + endpoints = (90+delta, 90-delta) + elif (direction == 'South'): + endpoints = (270+delta, 270-delta) + else: + endpoints = (0+delta, 0-delta) + return endpoints + + def movePacman(self, position, direction, image): + screenPosition = self.to_screen(position) + endpoints = self.getEndpoints( direction, position ) + r = PACMAN_SCALE * self.gridSize + moveCircle(image[0], screenPosition, r, endpoints) + refresh() + + def animatePacman(self, pacman, prevPacman, image): + if self.frameTime < 0: + print('Press any key to step forward, "q" to play') + keys = wait_for_keys() + if 'q' in keys: + self.frameTime = 0.1 + if self.frameTime > 0.01 or self.frameTime < 0: + start = time.time() + fx, fy = self.getPosition(prevPacman) + px, py = self.getPosition(pacman) + frames = 4.0 + for i in range(1,int(frames) + 1): + pos = px*i/frames + fx*(frames-i)/frames, py*i/frames + fy*(frames-i)/frames + self.movePacman(pos, self.getDirection(pacman), image) + refresh() + sleep(abs(self.frameTime) / frames) + else: + self.movePacman(self.getPosition(pacman), self.getDirection(pacman), image) + refresh() + + def getGhostColor(self, ghost, ghostIndex): + if ghost.scaredTimer > 0: + return SCARED_COLOR + else: + return GHOST_COLORS[ghostIndex] + + def drawGhost(self, ghost, agentIndex): + pos = self.getPosition(ghost) + dir = self.getDirection(ghost) + (screen_x, screen_y) = (self.to_screen(pos) ) + coords = [] + for (x, y) in GHOST_SHAPE: + coords.append((x*self.gridSize*GHOST_SIZE + screen_x, y*self.gridSize*GHOST_SIZE + screen_y)) + + colour = self.getGhostColor(ghost, agentIndex) + body = polygon(coords, colour, filled = 1) + WHITE = formatColor(1.0, 1.0, 1.0) + BLACK = formatColor(0.0, 0.0, 0.0) + + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + leftEye = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + rightEye = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2, WHITE, WHITE) + leftPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + rightPupil = circle((screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08, BLACK, BLACK) + ghostImageParts = [] + ghostImageParts.append(body) + ghostImageParts.append(leftEye) + ghostImageParts.append(rightEye) + ghostImageParts.append(leftPupil) + ghostImageParts.append(rightPupil) + + return ghostImageParts + + def moveEyes(self, pos, dir, eyes): + (screen_x, screen_y) = (self.to_screen(pos) ) + dx = 0 + dy = 0 + if dir == 'North': + dy = -0.2 + if dir == 'South': + dy = 0.2 + if dir == 'East': + dx = 0.2 + if dir == 'West': + dx = -0.2 + moveCircle(eyes[0],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[1],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx/1.5), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy/1.5)), self.gridSize*GHOST_SIZE*0.2) + moveCircle(eyes[2],(screen_x+self.gridSize*GHOST_SIZE*(-0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + moveCircle(eyes[3],(screen_x+self.gridSize*GHOST_SIZE*(0.3+dx), screen_y-self.gridSize*GHOST_SIZE*(0.3-dy)), self.gridSize*GHOST_SIZE*0.08) + + def moveGhost(self, ghost, ghostIndex, prevGhost, ghostImageParts): + old_x, old_y = self.to_screen(self.getPosition(prevGhost)) + new_x, new_y = self.to_screen(self.getPosition(ghost)) + delta = new_x - old_x, new_y - old_y + + for ghostImagePart in ghostImageParts: + move_by(ghostImagePart, delta) + refresh() + + if ghost.scaredTimer > 0: + color = SCARED_COLOR + else: + color = GHOST_COLORS[ghostIndex] + edit(ghostImageParts[0], ('fill', color), ('outline', color)) + self.moveEyes(self.getPosition(ghost), self.getDirection(ghost), ghostImageParts[-4:]) + refresh() + + def getPosition(self, agentState): + if agentState.configuration == None: return (-1000, -1000) + return agentState.getPosition() + + def getDirection(self, agentState): + if agentState.configuration == None: return Directions.STOP + return agentState.configuration.getDirection() + + def finish(self): + end_graphics() + + def to_screen(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + # Fixes some TK issue with off-center circles + def to_screen2(self, point): + ( x, y ) = point + #y = self.height - y + x = (x + 1)*self.gridSize + y = (self.height - y)*self.gridSize + return ( x, y ) + + def drawWalls(self, wallMatrix): + wallColor = WALL_COLOR + for xNum, x in enumerate(wallMatrix): + if self.capture and (xNum * 2) < wallMatrix.width: wallColor = TEAM_COLORS[0] + if self.capture and (xNum * 2) >= wallMatrix.width: wallColor = TEAM_COLORS[1] + + for yNum, cell in enumerate(x): + if cell: # There's a wall here + pos = (xNum, yNum) + screen = self.to_screen(pos) + screen2 = self.to_screen2(pos) + + # draw each quadrant of the square based on adjacent walls + wIsWall = self.isWall(xNum-1, yNum, wallMatrix) + eIsWall = self.isWall(xNum+1, yNum, wallMatrix) + nIsWall = self.isWall(xNum, yNum+1, wallMatrix) + sIsWall = self.isWall(xNum, yNum-1, wallMatrix) + nwIsWall = self.isWall(xNum-1, yNum+1, wallMatrix) + swIsWall = self.isWall(xNum-1, yNum-1, wallMatrix) + neIsWall = self.isWall(xNum+1, yNum+1, wallMatrix) + seIsWall = self.isWall(xNum+1, yNum-1, wallMatrix) + + # NE quadrant + if (not nIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (0,91), 'arc') + if (nIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (eIsWall) and (not neIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (180,271), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # NW quadrant + if (not nIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (90,181), 'arc') + if (nIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5)-1)), wallColor) + if (not nIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(-1)*WALL_RADIUS)), wallColor) + if (nIsWall) and (wIsWall) and (not nwIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (270,361), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(-1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(-1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-2)*WALL_RADIUS+1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(-0.5))), wallColor) + + # SE quadrant + if (not sIsWall) and (not eIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (270,361), 'arc') + if (sIsWall) and (not eIsWall): + # vertical line + line(add(screen, (self.gridSize*WALL_RADIUS, 0)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (eIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5+1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (eIsWall) and (not seIsWall): + # outer circle + circle(add(screen2, (self.gridSize*2*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (90,181), 'arc') + line(add(screen, (self.gridSize*2*WALL_RADIUS-1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*0.5, self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + # SW quadrant + if (not sIsWall) and (not wIsWall): + # inner circle + circle(screen2, WALL_RADIUS * self.gridSize, wallColor, wallColor, (180,271), 'arc') + if (sIsWall) and (not wIsWall): + # vertical line + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, 0)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5)+1)), wallColor) + if (not sIsWall) and (wIsWall): + # horizontal line + line(add(screen, (0, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5)-1, self.gridSize*(1)*WALL_RADIUS)), wallColor) + if (sIsWall) and (wIsWall) and (not swIsWall): + # outer circle + circle(add(screen2, (self.gridSize*(-2)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS)), WALL_RADIUS * self.gridSize-1, wallColor, wallColor, (0,91), 'arc') + line(add(screen, (self.gridSize*(-2)*WALL_RADIUS+1, self.gridSize*(1)*WALL_RADIUS)), add(screen, (self.gridSize*(-0.5), self.gridSize*(1)*WALL_RADIUS)), wallColor) + line(add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(2)*WALL_RADIUS-1)), add(screen, (self.gridSize*(-1)*WALL_RADIUS, self.gridSize*(0.5))), wallColor) + + def isWall(self, x, y, walls): + if x < 0 or y < 0: + return False + if x >= walls.width or y >= walls.height: + return False + return walls[x][y] + + def drawFood(self, foodMatrix ): + foodImages = [] + color = FOOD_COLOR + for xNum, x in enumerate(foodMatrix): + if self.capture and (xNum * 2) <= foodMatrix.width: color = TEAM_COLORS[0] + if self.capture and (xNum * 2) > foodMatrix.width: color = TEAM_COLORS[1] + imageRow = [] + foodImages.append(imageRow) + for yNum, cell in enumerate(x): + if cell: # There's food here + screen = self.to_screen((xNum, yNum )) + dot = circle( screen, + FOOD_SIZE * self.gridSize, + outlineColor = color, fillColor = color, + width = 1) + imageRow.append(dot) + else: + imageRow.append(None) + return foodImages + + def drawCapsules(self, capsules ): + capsuleImages = {} + for capsule in capsules: + ( screen_x, screen_y ) = self.to_screen(capsule) + dot = circle( (screen_x, screen_y), + CAPSULE_SIZE * self.gridSize, + outlineColor = CAPSULE_COLOR, + fillColor = CAPSULE_COLOR, + width = 1) + capsuleImages[capsule] = dot + return capsuleImages + + def removeFood(self, cell, foodImages ): + x, y = cell + remove_from_screen(foodImages[x][y]) + + def removeCapsule(self, cell, capsuleImages ): + x, y = cell + remove_from_screen(capsuleImages[(x, y)]) + + def drawExpandedCells(self, cells): + """ + Draws an overlay of expanded grid positions for search agents + """ + n = float(len(cells)) + baseColor = [1.0, 0.0, 0.0] + self.clearExpandedCells() + self.expandedCells = [] + for k, cell in enumerate(cells): + screenPos = self.to_screen( cell) + cellColor = formatColor(*[(n-k) * c * .5 / n + .25 for c in baseColor]) + block = square(screenPos, + 0.5 * self.gridSize, + color = cellColor, + filled = 1, behind=2) + self.expandedCells.append(block) + if self.frameTime < 0: + refresh() + + def clearExpandedCells(self): + if 'expandedCells' in dir(self) and len(self.expandedCells) > 0: + for cell in self.expandedCells: + remove_from_screen(cell) + + + def updateDistributions(self, distributions): + "Draws an agent's belief distributions" + # copy all distributions so we don't change their state + distributions = map(lambda x: x.copy(), distributions) + if self.distributionImages == None: + self.drawDistributions(self.previousState) + for x in range(len(self.distributionImages)): + for y in range(len(self.distributionImages[0])): + image = self.distributionImages[x][y] + weights = [dist[ (x,y) ] for dist in distributions] + + if sum(weights) != 0: + pass + # Fog of war + color = [0.0,0.0,0.0] + colors = GHOST_VEC_COLORS[1:] # With Pacman + if self.capture: colors = GHOST_VEC_COLORS + for weight, gcolor in zip(weights, colors): + color = [min(1.0, c + 0.95 * g * weight ** .3) for c,g in zip(color, gcolor)] + changeColor(image, formatColor(*color)) + refresh() + +class FirstPersonPacmanGraphics(PacmanGraphics): + def __init__(self, zoom = 1.0, showGhosts = True, capture = False, frameTime=0): + PacmanGraphics.__init__(self, zoom, frameTime=frameTime) + self.showGhosts = showGhosts + self.capture = capture + + def initialize(self, state, isBlue = False): + + self.isBlue = isBlue + PacmanGraphics.startGraphics(self, state) + # Initialize distribution images + walls = state.layout.walls + dist = [] + self.layout = state.layout + + # Draw the rest + self.distributionImages = None # initialize lazily + self.drawStaticObjects(state) + self.drawAgentObjects(state) + + # Information + self.previousState = state + + def lookAhead(self, config, state): + if config.getDirection() == 'Stop': + return + else: + pass + # Draw relevant ghosts + allGhosts = state.getGhostStates() + visibleGhosts = state.getVisibleGhosts() + for i, ghost in enumerate(allGhosts): + if ghost in visibleGhosts: + self.drawGhost(ghost, i) + else: + self.currentGhostImages[i] = None + + def getGhostColor(self, ghost, ghostIndex): + return GHOST_COLORS[ghostIndex] + + def getPosition(self, ghostState): + if not self.showGhosts and not ghostState.isPacman and ghostState.getPosition()[1] > 1: + return (-1000, -1000) + else: + return PacmanGraphics.getPosition(self, ghostState) + +def add(x, y): + return (x[0] + y[0], x[1] + y[1]) + + +# Saving graphical output +# ----------------------- +# Note: to make an animated gif from this postscript output, try the command: +# convert -delay 7 -loop 1 -compress lzw -layers optimize frame* out.gif +# convert is part of imagemagick (freeware) + +SAVE_POSTSCRIPT = False +POSTSCRIPT_OUTPUT_DIR = 'frames' +FRAME_NUMBER = 0 +import os + +def saveFrame(): + "Saves the current graphical output as a postscript file" + global SAVE_POSTSCRIPT, FRAME_NUMBER, POSTSCRIPT_OUTPUT_DIR + if not SAVE_POSTSCRIPT: return + if not os.path.exists(POSTSCRIPT_OUTPUT_DIR): os.mkdir(POSTSCRIPT_OUTPUT_DIR) + name = os.path.join(POSTSCRIPT_OUTPUT_DIR, 'frame_%08d.ps' % FRAME_NUMBER) + FRAME_NUMBER += 1 + writePostscript(name) # writes the current canvas diff --git a/school/cpsc481/project1/src/graphicsUtils.py b/school/cpsc481/project1/src/graphicsUtils.py new file mode 100644 index 0000000..22e6ae1 --- /dev/null +++ b/school/cpsc481/project1/src/graphicsUtils.py @@ -0,0 +1,402 @@ +# graphicsUtils.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import math +import random +import string +import time +import types +import tkinter +import os.path + +_Windows = sys.platform == 'win32' # True if on Win95/98/NT + +_root_window = None # The root window for graphics output +_canvas = None # The canvas which holds graphics +_canvas_xs = None # Size of canvas object +_canvas_ys = None +_canvas_x = None # Current position on canvas +_canvas_y = None +_canvas_col = None # Current colour (set to black below) +_canvas_tsize = 12 +_canvas_tserifs = 0 + +def formatColor(r, g, b): + return '#%02x%02x%02x' % (int(r * 255), int(g * 255), int(b * 255)) + +def colorToVector(color): + return list(map(lambda x: int(x, 16) / 256.0, [color[1:3], color[3:5], color[5:7]])) + +if _Windows: + _canvas_tfonts = ['times new roman', 'lucida console'] +else: + _canvas_tfonts = ['times', 'lucidasans-24'] + pass # XXX need defaults here + +def sleep(secs): + global _root_window + if _root_window == None: + time.sleep(secs) + else: + _root_window.update_idletasks() + _root_window.after(int(1000 * secs), _root_window.quit) + _root_window.mainloop() + +def begin_graphics(width=640, height=480, color=formatColor(0, 0, 0), title=None): + + global _root_window, _canvas, _canvas_x, _canvas_y, _canvas_xs, _canvas_ys, _bg_color + + # Check for duplicate call + if _root_window is not None: + # Lose the window. + _root_window.destroy() + + # Save the canvas size parameters + _canvas_xs, _canvas_ys = width - 1, height - 1 + _canvas_x, _canvas_y = 0, _canvas_ys + _bg_color = color + + # Create the root window + _root_window = tkinter.Tk() + _root_window.protocol('WM_DELETE_WINDOW', _destroy_window) + _root_window.title(title or 'Graphics Window') + _root_window.resizable(0, 0) + + # Create the canvas object + try: + _canvas = tkinter.Canvas(_root_window, width=width, height=height) + _canvas.pack() + draw_background() + _canvas.update() + except: + _root_window = None + raise + + # Bind to key-down and key-up events + _root_window.bind( "", _keypress ) + _root_window.bind( "", _keyrelease ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _clear_keys ) + _root_window.bind( "", _leftclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _rightclick ) + _root_window.bind( "", _ctrl_leftclick) + _clear_keys() + +_leftclick_loc = None +_rightclick_loc = None +_ctrl_leftclick_loc = None + +def _leftclick(event): + global _leftclick_loc + _leftclick_loc = (event.x, event.y) + +def _rightclick(event): + global _rightclick_loc + _rightclick_loc = (event.x, event.y) + +def _ctrl_leftclick(event): + global _ctrl_leftclick_loc + _ctrl_leftclick_loc = (event.x, event.y) + +def wait_for_click(): + while True: + global _leftclick_loc + global _rightclick_loc + global _ctrl_leftclick_loc + if _leftclick_loc != None: + val = _leftclick_loc + _leftclick_loc = None + return val, 'left' + if _rightclick_loc != None: + val = _rightclick_loc + _rightclick_loc = None + return val, 'right' + if _ctrl_leftclick_loc != None: + val = _ctrl_leftclick_loc + _ctrl_leftclick_loc = None + return val, 'ctrl_left' + sleep(0.05) + +def draw_background(): + corners = [(0,0), (0, _canvas_ys), (_canvas_xs, _canvas_ys), (_canvas_xs, 0)] + polygon(corners, _bg_color, fillColor=_bg_color, filled=True, smoothed=False) + +def _destroy_window(event=None): + sys.exit(0) +# global _root_window +# _root_window.destroy() +# _root_window = None + #print("DESTROY") + +def end_graphics(): + global _root_window, _canvas, _mouse_enabled + try: + try: + sleep(1) + if _root_window != None: + _root_window.destroy() + except SystemExit as e: + print('Ending graphics raised an exception:', e) + finally: + _root_window = None + _canvas = None + _mouse_enabled = 0 + _clear_keys() + +def clear_screen(background=None): + global _canvas_x, _canvas_y + _canvas.delete('all') + draw_background() + _canvas_x, _canvas_y = 0, _canvas_ys + +def polygon(coords, outlineColor, fillColor=None, filled=1, smoothed=1, behind=0, width=1): + c = [] + for coord in coords: + c.append(coord[0]) + c.append(coord[1]) + if fillColor == None: fillColor = outlineColor + if filled == 0: fillColor = "" + poly = _canvas.create_polygon(c, outline=outlineColor, fill=fillColor, smooth=smoothed, width=width) + if behind > 0: + _canvas.tag_lower(poly, behind) # Higher should be more visible + return poly + +def square(pos, r, color, filled=1, behind=0): + x, y = pos + coords = [(x - r, y - r), (x + r, y - r), (x + r, y + r), (x - r, y + r)] + return polygon(coords, color, color, filled, 0, behind=behind) + +def circle(pos, r, outlineColor, fillColor=None, endpoints=None, style='pieslice', width=2): + x, y = pos + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + return _canvas.create_arc(x0, y0, x1, y1, outline=outlineColor, fill=fillColor or outlineColor, + extent=e[1] - e[0], start=e[0], style=style, width=width) + +def image(pos, file="../../blueghost.gif"): + x, y = pos + # img = PhotoImage(file=file) + return _canvas.create_image(x, y, image = tkinter.PhotoImage(file=file), anchor = tkinter.NW) + + +def refresh(): + _canvas.update_idletasks() + +def moveCircle(id, pos, r, endpoints=None): + global _canvas_x, _canvas_y + + x, y = pos +# x0, x1 = x - r, x + r + 1 +# y0, y1 = y - r, y + r + 1 + x0, x1 = x - r - 1, x + r + y0, y1 = y - r - 1, y + r + if endpoints == None: + e = [0, 359] + else: + e = list(endpoints) + while e[0] > e[1]: e[1] = e[1] + 360 + + if os.path.isfile('flag'): + edit(id, ('extent', e[1] - e[0])) + else: + edit(id, ('start', e[0]), ('extent', e[1] - e[0])) + move_to(id, x0, y0) + +def edit(id, *args): + _canvas.itemconfigure(id, **dict(args)) + +def text(pos, color, contents, font='Helvetica', size=12, style='normal', anchor="nw"): + global _canvas_x, _canvas_y + x, y = pos + font = (font, str(size), style) + return _canvas.create_text(x, y, fill=color, text=contents, font=font, anchor=anchor) + +def changeText(id, newText, font=None, size=12, style='normal'): + _canvas.itemconfigure(id, text=newText) + if font != None: + _canvas.itemconfigure(id, font=(font, '-%d' % size, style)) + +def changeColor(id, newColor): + _canvas.itemconfigure(id, fill=newColor) + +def line(here, there, color=formatColor(0, 0, 0), width=2): + x0, y0 = here[0], here[1] + x1, y1 = there[0], there[1] + return _canvas.create_line(x0, y0, x1, y1, fill=color, width=width) + +############################################################################## +### Keypress handling ######################################################## +############################################################################## + +# We bind to key-down and key-up events. + +_keysdown = {} +_keyswaiting = {} +# This holds an unprocessed key release. We delay key releases by up to +# one call to keys_pressed() to get round a problem with auto repeat. +_got_release = None + +def _keypress(event): + global _got_release + #remap_arrows(event) + _keysdown[event.keysym] = 1 + _keyswaiting[event.keysym] = 1 +# print(event.char, event.keycode) + _got_release = None + +def _keyrelease(event): + global _got_release + #remap_arrows(event) + try: + del _keysdown[event.keysym] + except: + pass + _got_release = 1 + +def remap_arrows(event): + # TURN ARROW PRESSES INTO LETTERS (SHOULD BE IN KEYBOARD AGENT) + if event.char in ['a', 's', 'd', 'w']: + return + if event.keycode in [37, 101]: # LEFT ARROW (win / x) + event.char = 'a' + if event.keycode in [38, 99]: # UP ARROW + event.char = 'w' + if event.keycode in [39, 102]: # RIGHT ARROW + event.char = 'd' + if event.keycode in [40, 104]: # DOWN ARROW + event.char = 's' + +def _clear_keys(event=None): + global _keysdown, _got_release, _keyswaiting + _keysdown = {} + _keyswaiting = {} + _got_release = None + +def keys_pressed(d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + d_o_e(d_w) + if _got_release: + d_o_e(d_w) + return _keysdown.keys() + +def keys_waiting(): + global _keyswaiting + keys = _keyswaiting.keys() + _keyswaiting = {} + return keys + +# Block for a list of keys... + +def wait_for_keys(): + keys = [] + while keys == []: + keys = keys_pressed() + sleep(0.05) + return keys + +def remove_from_screen(x, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + _canvas.delete(x) + d_o_e(d_w) + +def _adjust_coords(coord_list, x, y): + for i in range(0, len(coord_list), 2): + coord_list[i] = coord_list[i] + x + coord_list[i + 1] = coord_list[i + 1] + y + return coord_list + +def move_to(object, x, y=None, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT): + if y is None: + try: x, y = x + except: raise 'incomprehensible coordinates' + + horiz = True + newCoords = [] + current_x, current_y = _canvas.coords(object)[0:2] # first point + for coord in _canvas.coords(object): + if horiz: + inc = x - current_x + else: + inc = y - current_y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + +def move_by(object, x, y=None, + d_o_e=lambda arg: _root_window.dooneevent(arg), + d_w=tkinter._tkinter.DONT_WAIT, lift=False): + if y is None: + try: x, y = x + except: raise Exception('incomprehensible coordinates') + + horiz = True + newCoords = [] + for coord in _canvas.coords(object): + if horiz: + inc = x + else: + inc = y + horiz = not horiz + + newCoords.append(coord + inc) + + _canvas.coords(object, *newCoords) + d_o_e(d_w) + if lift: + _canvas.tag_raise(object) + +def writePostscript(filename): + "Writes the current canvas to a postscript file." + psfile = open(filename, 'w') + psfile.write(_canvas.postscript(pageanchor='sw', + y='0.c', + x='0.c')) + psfile.close() + +ghost_shape = [ + (0, - 0.5), + (0.25, - 0.75), + (0.5, - 0.5), + (0.75, - 0.75), + (0.75, 0.5), + (0.5, 0.75), + (- 0.5, 0.75), + (- 0.75, 0.5), + (- 0.75, - 0.75), + (- 0.5, - 0.5), + (- 0.25, - 0.75) + ] + +if __name__ == '__main__': + begin_graphics() + clear_screen() + ghost_shape = [(x * 10 + 20, y * 10 + 20) for x, y in ghost_shape] + g = polygon(ghost_shape, formatColor(1, 1, 1)) + move_to(g, (50, 50)) + circle((150, 150), 20, formatColor(0.7, 0.3, 0.0), endpoints=[15, - 15]) + sleep(2) diff --git a/school/cpsc481/project1/src/keyboardAgents.py b/school/cpsc481/project1/src/keyboardAgents.py new file mode 100644 index 0000000..624021d --- /dev/null +++ b/school/cpsc481/project1/src/keyboardAgents.py @@ -0,0 +1,84 @@ +# keyboardAgents.py +# ----------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Directions +import random + +class KeyboardAgent(Agent): + """ + An agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'a' + EAST_KEY = 'd' + NORTH_KEY = 'w' + SOUTH_KEY = 's' + STOP_KEY = 'q' + + def __init__( self, index = 0 ): + + self.lastMove = Directions.STOP + self.index = index + self.keys = [] + + def getAction( self, state): + from graphicsUtils import keys_waiting + from graphicsUtils import keys_pressed + keys = list(keys_waiting()) + list(keys_pressed()) + if keys != []: + self.keys = keys + + legal = state.getLegalActions(self.index) + move = self.getMove(legal) + + if move == Directions.STOP: + # Try to move in the same direction as before + if self.lastMove in legal: + move = self.lastMove + + if (self.STOP_KEY in self.keys) and Directions.STOP in legal: move = Directions.STOP + + if move not in legal: + move = random.choice(legal) + + self.lastMove = move + return move + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys or 'Left' in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys or 'Right' in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys or 'Up' in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys or 'Down' in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move + +class KeyboardAgent2(KeyboardAgent): + """ + A second agent controlled by the keyboard. + """ + # NOTE: Arrow keys also work. + WEST_KEY = 'j' + EAST_KEY = "l" + NORTH_KEY = 'i' + SOUTH_KEY = 'k' + STOP_KEY = 'u' + + def getMove(self, legal): + move = Directions.STOP + if (self.WEST_KEY in self.keys) and Directions.WEST in legal: move = Directions.WEST + if (self.EAST_KEY in self.keys) and Directions.EAST in legal: move = Directions.EAST + if (self.NORTH_KEY in self.keys) and Directions.NORTH in legal: move = Directions.NORTH + if (self.SOUTH_KEY in self.keys) and Directions.SOUTH in legal: move = Directions.SOUTH + return move diff --git a/school/cpsc481/project1/src/layout.py b/school/cpsc481/project1/src/layout.py new file mode 100644 index 0000000..ebba32a --- /dev/null +++ b/school/cpsc481/project1/src/layout.py @@ -0,0 +1,150 @@ +# layout.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from util import manhattanDistance +from game import Grid +import os +import random +from functools import reduce + +VISIBILITY_MATRIX_CACHE = {} + +class Layout: + """ + A Layout manages the static information about the game board. + """ + + def __init__(self, layoutText): + self.width = len(layoutText[0]) + self.height= len(layoutText) + self.walls = Grid(self.width, self.height, False) + self.food = Grid(self.width, self.height, False) + self.capsules = [] + self.agentPositions = [] + self.numGhosts = 0 + self.processLayoutText(layoutText) + self.layoutText = layoutText + self.totalFood = len(self.food.asList()) + # self.initializeVisibilityMatrix() + + def getNumGhosts(self): + return self.numGhosts + + def initializeVisibilityMatrix(self): + global VISIBILITY_MATRIX_CACHE + if reduce(str.__add__, self.layoutText) not in VISIBILITY_MATRIX_CACHE: + from game import Directions + vecs = [(-0.5,0), (0.5,0),(0,-0.5),(0,0.5)] + dirs = [Directions.NORTH, Directions.SOUTH, Directions.WEST, Directions.EAST] + vis = Grid(self.width, self.height, {Directions.NORTH:set(), Directions.SOUTH:set(), Directions.EAST:set(), Directions.WEST:set(), Directions.STOP:set()}) + for x in range(self.width): + for y in range(self.height): + if self.walls[x][y] == False: + for vec, direction in zip(vecs, dirs): + dx, dy = vec + nextx, nexty = x + dx, y + dy + while (nextx + nexty) != int(nextx) + int(nexty) or not self.walls[int(nextx)][int(nexty)] : + vis[x][y][direction].add((nextx, nexty)) + nextx, nexty = x + dx, y + dy + self.visibility = vis + VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] = vis + else: + self.visibility = VISIBILITY_MATRIX_CACHE[reduce(str.__add__, self.layoutText)] + + def isWall(self, pos): + x, col = pos + return self.walls[x][col] + + def getRandomLegalPosition(self): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + while self.isWall( (x, y) ): + x = random.choice(range(self.width)) + y = random.choice(range(self.height)) + return (x,y) + + def getRandomCorner(self): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + return random.choice(poses) + + def getFurthestCorner(self, pacPos): + poses = [(1,1), (1, self.height - 2), (self.width - 2, 1), (self.width - 2, self.height - 2)] + dist, pos = max([(manhattanDistance(p, pacPos), p) for p in poses]) + return pos + + def isVisibleFrom(self, ghostPos, pacPos, pacDirection): + row, col = [int(x) for x in pacPos] + return ghostPos in self.visibility[row][col][pacDirection] + + def __str__(self): + return "\n".join(self.layoutText) + + def deepCopy(self): + return Layout(self.layoutText[:]) + + def processLayoutText(self, layoutText): + """ + Coordinates are flipped from the input format to the (x,y) convention here + + The shape of the maze. Each character + represents a different type of object. + % - Wall + . - Food + o - Capsule + G - Ghost + P - Pacman + Other characters are ignored. + """ + maxY = self.height - 1 + for y in range(self.height): + for x in range(self.width): + layoutChar = layoutText[maxY - y][x] + self.processLayoutChar(x, y, layoutChar) + self.agentPositions.sort() + self.agentPositions = [ ( i == 0, pos) for i, pos in self.agentPositions] + + def processLayoutChar(self, x, y, layoutChar): + if layoutChar == '%': + self.walls[x][y] = True + elif layoutChar == '.': + self.food[x][y] = True + elif layoutChar == 'o': + self.capsules.append((x, y)) + elif layoutChar == 'P': + self.agentPositions.append( (0, (x, y) ) ) + elif layoutChar in ['G']: + self.agentPositions.append( (1, (x, y) ) ) + self.numGhosts += 1 + elif layoutChar in ['1', '2', '3', '4']: + self.agentPositions.append( (int(layoutChar), (x,y))) + self.numGhosts += 1 +def getLayout(name, back = 2): + if name.endswith('.lay'): + layout = tryToLoad('layouts/' + name) + if layout == None: layout = tryToLoad(name) + else: + layout = tryToLoad('layouts/' + name + '.lay') + if layout == None: layout = tryToLoad(name + '.lay') + if layout == None and back >= 0: + curdir = os.path.abspath('.') + os.chdir('..') + layout = getLayout(name, back -1) + os.chdir(curdir) + return layout + +def tryToLoad(fullname): + if(not os.path.exists(fullname)): return None + f = open(fullname) + try: return Layout([line.strip() for line in f]) + finally: f.close() diff --git a/school/cpsc481/project1/src/layouts/bigCorners.lay b/school/cpsc481/project1/src/layouts/bigCorners.lay new file mode 100644 index 0000000..4d89d7b --- /dev/null +++ b/school/cpsc481/project1/src/layouts/bigCorners.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % %.% +% %%%%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % +% % % % % % %%% %%% %%% % % % % % % +% % % % % %% % % % % % % % % % +% % %%%%% % %%% %%% % %%% %%% %%%%% +% % % % % % % % % % % +% %%% % % % %%% %%% %%%%%%%%% % %%% +% % % % % % % +% %%% %%%%%%%%%%%%%%%%%%%%% % % %%% % +% % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % %P % % % % % % +% %%% %%% %%% % %%% % % %%%%% % %%%%% +% % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % +% % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/bigMaze.lay b/school/cpsc481/project1/src/layouts/bigMaze.lay new file mode 100644 index 0000000..e11fade --- /dev/null +++ b/school/cpsc481/project1/src/layouts/bigMaze.lay @@ -0,0 +1,37 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% % % % % % % % +% %%%%%%% % %%% % %%% %%% %%%%%%% % % +% % % % % % % % +%%%%% %%%%% %%% % % % %%% %%%%% % %%% +% % % % % % % % % % % % % % +% %%% % % % %%% %%%%% %%% % %%% %%% % +% % % % % % % % % +%%% %%%%%%%%% %%%%%%% %%% %%% % % % % +% % % % % % % +% % %%%%% % %%% % % %%% % %%% %%% % % +% % % % % % % % % % % % % % +% % % %%%%%%% % %%%%%%%%% %%% % %%% % +% % % % % % % % % % +%%% %%% % %%%%% %%%%% %%% %%% %%%%% % +% % % % % % % % % % % % +% % % % % %%% %%% %%% %%% % % % % % % +% % % % % % % % % +%%% %%%%%%% % % %%%%% %%% % %%% %%%%% +% % % % % % % % % % +%%%%% % % %%%%%%%%% %%%%%%%%%%% % %%% +% % % % % % % % % +% %%% %%%%% %%%%%%%%% %%%%% % % %%% % +% % % % % % % +% % % %%%%% %%% % % % % %%%%%%%%%%%%% +% % % % % % % % % % % % +% % %%% %%% % % % %%%%%%%%% %%% % % % +% % % % % % % % % % % % % +% %%% %%% %%%%% %%% % % %%%%% % %%%%% +% % % % % % % % % +%%% % %%%%% %%%%% %%% %%% % %%% % %%% +% % % % % % % % % % % % % % % +% % %%% % % % % %%%%%%%%% % % % % % % +% % % % % % +% % % % %%% %%% %%%%%%% %%% %%% %%% % +%.% % % % % % % % P% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/bigSafeSearch.lay b/school/cpsc481/project1/src/layouts/bigSafeSearch.lay new file mode 100644 index 0000000..b5fd414 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/bigSafeSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.%.........%% G % o%%%%.....% +%.%.%%%%%%%.%%%%%% %%%%%%%.%%.% +%............%...%............% +%%%%%...%%%.. ..%.%...%.%%% +%o%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +% ..........Po...%...%. o% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/bigSearch.lay b/school/cpsc481/project1/src/layouts/bigSearch.lay new file mode 100644 index 0000000..bb59eb8 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/bigSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.....%.................%.....% +%.%%%.%.%%%.%%%%%%%.%%%.%.....% +%.%...%.%......%......%.%.....% +%...%%%.%.%%%%.%.%%%%...%%%...% +%%%.%.%.%.%......%..%.%...%.%%% +%...%.%%%.%.%%% %%%.%.%%%.%...% +%.%%%.......% %.......%%%.% +%...%.%%%%%.%%%%%%%.%.%%%.%...% +%%%.%...%.%....%....%.%...%.%%% +%...%%%.%.%%%%.%.%%%%.%.%%%...% +%.......%......%......%.....%.% +%.....%.%%%.%%%%%%%.%%%.%.%%%.% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/boxSearch.lay b/school/cpsc481/project1/src/layouts/boxSearch.lay new file mode 100644 index 0000000..4a113fc --- /dev/null +++ b/school/cpsc481/project1/src/layouts/boxSearch.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%% +%. . . . . % % +% % % +%. . . . . %G% +% % % +%. . . . . % % +% % % +%. . . . . % % +% P %G% +%. . . . . % % +% % % +%. . . . . % % +% % % +%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/capsuleClassic.lay b/school/cpsc481/project1/src/layouts/capsuleClassic.lay new file mode 100644 index 0000000..06a5c51 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/capsuleClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%% +%G. G ....% +%.% % %%%%%% %.%%.% +%.%o% % o% %.o%.% +%.%%%.% %%% %..%.% +%..... P %..%G% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/contestClassic.lay b/school/cpsc481/project1/src/layouts/contestClassic.lay new file mode 100644 index 0000000..84c8733 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/contestClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%...... G GG%......% +%.%.%%.%% %%%.%%.%.% +%.%....% ooo%.%..%.% +%.%.%%.% %% %.%.%%.% +%o%......P....%....% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/contoursMaze.lay b/school/cpsc481/project1/src/layouts/contoursMaze.lay new file mode 100644 index 0000000..a068956 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/contoursMaze.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%%% +% % +% % +% % +% % +% P % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/greedySearch.lay b/school/cpsc481/project1/src/layouts/greedySearch.lay new file mode 100644 index 0000000..4072363 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/greedySearch.lay @@ -0,0 +1,8 @@ +%%%%%% +%....% +% %%.% +% %%.% +%.P .% +%.%%%% +%....% +%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/mediumClassic.lay b/school/cpsc481/project1/src/layouts/mediumClassic.lay new file mode 100644 index 0000000..33c5db8 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumClassic.lay @@ -0,0 +1,11 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%....% +%.%%.%.%%%%%%.%.%%.% +%.%..............%.% +%.%.%%.%% %%.%%.%.% +%......%G G%......% +%.%.%%.%%%%%%.%%.%.% +%.%..............%.% +%.%%.%.%%%%%%.%.%%.% +%....%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/mediumCorners.lay b/school/cpsc481/project1/src/layouts/mediumCorners.lay new file mode 100644 index 0000000..6a39756 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumCorners.lay @@ -0,0 +1,14 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/mediumDottedMaze.lay b/school/cpsc481/project1/src/layouts/mediumDottedMaze.lay new file mode 100644 index 0000000..103f818 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumDottedMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %% ... % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %% %% %% ... % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% ... % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% ... % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % ... % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% ...... % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/mediumMaze.lay b/school/cpsc481/project1/src/layouts/mediumMaze.lay new file mode 100644 index 0000000..55c1236 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/mediumSafeSearch.lay b/school/cpsc481/project1/src/layouts/mediumSafeSearch.lay new file mode 100644 index 0000000..e7d6b1c --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumSafeSearch.lay @@ -0,0 +1,6 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%.% ....%% G %%%%%% o%%.% +%.%o%%%%%%%.%%%%%%% %%%%%.% +% %%%.%%%%%.%%%%%%%.%%%.%.%%%.% +% ..........Po...%.........% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/mediumScaryMaze.lay b/school/cpsc481/project1/src/layouts/mediumScaryMaze.lay new file mode 100644 index 0000000..65d4c33 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumScaryMaze.lay @@ -0,0 +1,18 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%% %%% %%%%%%%% % +% %% % % %%% %%% %%GG % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % % %%GG %% % +% %% % % % % % %%%%% %%% %%%%%% % +% %% % % % % %% %%%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%% %% %%%%%%% %% %%%%%% % +%%%%%% % % %% %% % +% %%%%%% %% %% %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%% %%%%% %%%%%% % +%%%%%%%% % %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/mediumSearch.lay b/school/cpsc481/project1/src/layouts/mediumSearch.lay new file mode 100644 index 0000000..2f8af42 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/mediumSearch.lay @@ -0,0 +1,8 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%%%%............% +%%%.%...%%%.........%.%...%.%%% +%...%%%.%.%%%%.%.%%%%%%.%%%...% +%.%.....%......%......%.....%.% +%.%%%.%%%%%.%%%%%%%.%%%.%.%%%%% +%.....%........P....%...%.....% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/minimaxClassic.lay b/school/cpsc481/project1/src/layouts/minimaxClassic.lay new file mode 100644 index 0000000..a547397 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/minimaxClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%%% +%.P G% +% %.%G%%% +%G %%% +%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/oddSearch.lay b/school/cpsc481/project1/src/layouts/oddSearch.lay new file mode 100644 index 0000000..2ddbc9a --- /dev/null +++ b/school/cpsc481/project1/src/layouts/oddSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%...%.........%%...% +%.%.%.%%%%%%%%%%.%.% +%..................% +%%%%%%%%.%.%%%%%%%P% +%%%%%%%%....... % +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/openClassic.lay b/school/cpsc481/project1/src/layouts/openClassic.lay new file mode 100644 index 0000000..6760b42 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/openClassic.lay @@ -0,0 +1,9 @@ +%%%%%%%%%%%%%%%%%%%%%%%%% +%.. P .... .... % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... G % +%.. ... ... ... ... % +%.. ... ... ... ... % +%.. .... .... o% +%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/openMaze.lay b/school/cpsc481/project1/src/layouts/openMaze.lay new file mode 100644 index 0000000..5dee689 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/openMaze.lay @@ -0,0 +1,23 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% % % +% % % +% % % +% % % +% % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +% % % % +%%%%%%%%%%%%%% %%%%%%%%%%%%%%%%% +% % % +% % % +% % % +% % +% % +% % +%. % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/openSearch.lay b/school/cpsc481/project1/src/layouts/openSearch.lay new file mode 100644 index 0000000..f02d21d --- /dev/null +++ b/school/cpsc481/project1/src/layouts/openSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%..................% +%..................% +%........P.........% +%..................% +%..................% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/originalClassic.lay b/school/cpsc481/project1/src/layouts/originalClassic.lay new file mode 100644 index 0000000..b2770c5 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/originalClassic.lay @@ -0,0 +1,27 @@ +%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o%%%%.%%%%%.%%.%%%%%.%%%%o% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%..........................% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%.%%%%.%%.%%%%%%%%.%%.%%%%.% +%......%%....%%....%%......% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.%%%%% %% %%%%%.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%% %%%% %.%%%%%% +% . %G GG G% . % +%%%%%%.% %%%%%%%%%% %.%%%%%% +%%%%%%.% %.%%%%%% +%%%%%%.% %%%%%%%%%% %.%%%%%% +%............%%............% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%.%%%%.%%%%%.%%.%%%%%.%%%%.% +%o..%%....... .......%%..o% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%%%.%%.%%.%%%%%%%%.%%.%%.%%% +%......%%....%%....%%......% +%.%%%%%%%%%%.%%.%%%%%%%%%%.% +%.............P............% +%%%%%%%%%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/powerClassic.lay b/school/cpsc481/project1/src/layouts/powerClassic.lay new file mode 100644 index 0000000..3f3d983 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/powerClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%o....o%GGGG%o....o% +%..%...%% %%...%..% +%.%o.%........%.o%.% +%.o%.%.%%%%%%.%.%o.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/smallClassic.lay b/school/cpsc481/project1/src/layouts/smallClassic.lay new file mode 100644 index 0000000..ce6c1d9 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/smallClassic.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%......%G G%......% +%.%%...%% %%...%%.% +%.%o.%........%.o%.% +%.%%.%.%%%%%%.%.%%.% +%........P.........% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/smallMaze.lay b/school/cpsc481/project1/src/layouts/smallMaze.lay new file mode 100644 index 0000000..72d3ffc --- /dev/null +++ b/school/cpsc481/project1/src/layouts/smallMaze.lay @@ -0,0 +1,10 @@ +%%%%%%%%%%%%%%%%%%%%%% +% %% % % % +% %%%%%% % %%%%%% % +%%%%%% P % % +% % %%%%%% %% %%%%% +% %%%% % % % +% %%% %%% % % +%%%%%%%%%% %%%%%% % +%. %% % +%%%%%%%%%%%%%%%%%%%%%% \ No newline at end of file diff --git a/school/cpsc481/project1/src/layouts/smallSafeSearch.lay b/school/cpsc481/project1/src/layouts/smallSafeSearch.lay new file mode 100644 index 0000000..b97feaa --- /dev/null +++ b/school/cpsc481/project1/src/layouts/smallSafeSearch.lay @@ -0,0 +1,15 @@ +%%%%%%%%% +%.. % G % +%%% %%%%% +% % +%%%%%%% % +% % +% %%%%% % +% % % +%%%%% % % +% %o% +% %%%%%%% +% .% +%%%%%%%.% +%Po .% +%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/smallSearch.lay b/school/cpsc481/project1/src/layouts/smallSearch.lay new file mode 100644 index 0000000..c2321d4 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/smallSearch.lay @@ -0,0 +1,5 @@ +%%%%%%%%%%%%%%%%%%%% +%. ...P .% +%.%%.%%.%%.%%.%% %.% +% %% %..... %.% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/testClassic.lay b/school/cpsc481/project1/src/layouts/testClassic.lay new file mode 100644 index 0000000..4b3ffca --- /dev/null +++ b/school/cpsc481/project1/src/layouts/testClassic.lay @@ -0,0 +1,10 @@ +%%%%% +% . % +%.G.% +% . % +%. .% +% % +% .% +% % +%P .% +%%%%% diff --git a/school/cpsc481/project1/src/layouts/testMaze.lay b/school/cpsc481/project1/src/layouts/testMaze.lay new file mode 100644 index 0000000..4d259a4 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/testMaze.lay @@ -0,0 +1,3 @@ +%%%%%%%%%% +%. P% +%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/testSearch.lay b/school/cpsc481/project1/src/layouts/testSearch.lay new file mode 100644 index 0000000..25bad23 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/testSearch.lay @@ -0,0 +1,5 @@ +%%%%% +%.P % +%%% % +%. % +%%%%% diff --git a/school/cpsc481/project1/src/layouts/tinyCorners.lay b/school/cpsc481/project1/src/layouts/tinyCorners.lay new file mode 100644 index 0000000..526c880 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/tinyCorners.lay @@ -0,0 +1,8 @@ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/tinyMaze.lay b/school/cpsc481/project1/src/layouts/tinyMaze.lay new file mode 100644 index 0000000..f7035a5 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/tinyMaze.lay @@ -0,0 +1,7 @@ +%%%%%%% +% P% +% %%% % +% % % +%% %% +%. %%%% +%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/tinySafeSearch.lay b/school/cpsc481/project1/src/layouts/tinySafeSearch.lay new file mode 100644 index 0000000..fea6860 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/tinySafeSearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +% G %...% +%%%%%%% % +%Po % +%.%%.%%.% +%.%%....% +%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/tinySearch.lay b/school/cpsc481/project1/src/layouts/tinySearch.lay new file mode 100644 index 0000000..c51f4b0 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/tinySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%% +%.. ..% +%%%%.%% % +% P % +%.%% %%.% +%.%. .% +%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/trappedClassic.lay b/school/cpsc481/project1/src/layouts/trappedClassic.lay new file mode 100644 index 0000000..289557f --- /dev/null +++ b/school/cpsc481/project1/src/layouts/trappedClassic.lay @@ -0,0 +1,5 @@ +%%%%%%%% +% P G% +%G%%%%%% +%.... % +%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/trickyClassic.lay b/school/cpsc481/project1/src/layouts/trickyClassic.lay new file mode 100644 index 0000000..ffa156c --- /dev/null +++ b/school/cpsc481/project1/src/layouts/trickyClassic.lay @@ -0,0 +1,13 @@ +%%%%%%%%%%%%%%%%%%%% +%o...%........%...o% +%.%%.%.%%..%%.%.%%.% +%.%.....%..%.....%.% +%.%.%%.%% %%.%%.%.% +%...... GGGG%.%....% +%.%....%%%%%%.%..%.% +%.%....% oo%.%..%.% +%.%....% %%%%.%..%.% +%.%...........%..%.% +%.%%.%.%%%%%%.%.%%.% +%o...%...P....%...o% +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/layouts/trickySearch.lay b/school/cpsc481/project1/src/layouts/trickySearch.lay new file mode 100644 index 0000000..4a607e6 --- /dev/null +++ b/school/cpsc481/project1/src/layouts/trickySearch.lay @@ -0,0 +1,7 @@ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% diff --git a/school/cpsc481/project1/src/pacman.py b/school/cpsc481/project1/src/pacman.py new file mode 100644 index 0000000..0a527c2 --- /dev/null +++ b/school/cpsc481/project1/src/pacman.py @@ -0,0 +1,684 @@ +# pacman.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +Pacman.py holds the logic for the classic pacman game along with the main +code to run a game. This file is divided into three sections: + + (i) Your interface to the pacman world: + Pacman is a complex environment. You probably don't want to + read through all of the code we wrote to make the game runs + correctly. This section contains the parts of the code + that you will need to understand in order to complete the + project. There is also some code in game.py that you should + understand. + + (ii) The hidden secrets of pacman: + This section contains all of the logic code that the pacman + environment uses to decide who can move where, who dies when + things collide, etc. You shouldn't need to read this section + of code, but you can if you want. + + (iii) Framework to start a game: + The final section contains the code for reading the command + you use to set up the game, then starting up a new game, along with + linking in all the external parts (agent functions, graphics). + Check this section out to see all the options available to you. + +To play your first game, type 'python pacman.py' from the command line. +The keys are 'a', 's', 'd', and 'w' to move (or arrow keys). Have fun! +""" +from game import GameStateData +from game import Game +from game import Directions +from game import Actions +from util import nearestPoint +from util import manhattanDistance +import util, layout +import sys, types, time, random, os + +################################################### +# YOUR INTERFACE TO THE PACMAN WORLD: A GameState # +################################################### + +class GameState: + """ + A GameState specifies the full game state, including the food, capsules, + agent configurations and score changes. + + GameStates are used by the Game object to capture the actual state of the game and + can be used by agents to reason about the game. + + Much of the information in a GameState is stored in a GameStateData object. We + strongly suggest that you access that data via the accessor methods below rather + than referring to the GameStateData object directly. + + Note that in classic Pacman, Pacman is always agent 0. + """ + + #################################################### + # Accessor methods: use these to access state data # + #################################################### + + # static variable keeps track of which states have had getLegalActions called + explored = set() + def getAndResetExplored(): + tmp = GameState.explored.copy() + GameState.explored = set() + return tmp + getAndResetExplored = staticmethod(getAndResetExplored) + + def getLegalActions( self, agentIndex=0 ): + """ + Returns the legal actions for the agent specified. + """ +# GameState.explored.add(self) + if self.isWin() or self.isLose(): return [] + + if agentIndex == 0: # Pacman is moving + return PacmanRules.getLegalActions( self ) + else: + return GhostRules.getLegalActions( self, agentIndex ) + + def generateSuccessor( self, agentIndex, action): + """ + Returns the successor state after the specified agent takes the action. + """ + # Check that successors exist + if self.isWin() or self.isLose(): raise Exception('Can\'t generate a successor of a terminal state.') + + # Copy current state + state = GameState(self) + + # Let agent's logic deal with its action's effects on the board + if agentIndex == 0: # Pacman is moving + state.data._eaten = [False for i in range(state.getNumAgents())] + PacmanRules.applyAction( state, action ) + else: # A ghost is moving + GhostRules.applyAction( state, action, agentIndex ) + + # Time passes + if agentIndex == 0: + state.data.scoreChange += -TIME_PENALTY # Penalty for waiting around + else: + GhostRules.decrementTimer( state.data.agentStates[agentIndex] ) + + # Resolve multi-agent effects + GhostRules.checkDeath( state, agentIndex ) + + # Book keeping + state.data._agentMoved = agentIndex + state.data.score += state.data.scoreChange + GameState.explored.add(self) + GameState.explored.add(state) + return state + + def getLegalPacmanActions( self ): + return self.getLegalActions( 0 ) + + def generatePacmanSuccessor( self, action ): + """ + Generates the successor state after the specified pacman move + """ + return self.generateSuccessor( 0, action ) + + def getPacmanState( self ): + """ + Returns an AgentState object for pacman (in game.py) + + state.pos gives the current position + state.direction gives the travel vector + """ + return self.data.agentStates[0].copy() + + def getPacmanPosition( self ): + return self.data.agentStates[0].getPosition() + + def getGhostStates( self ): + return self.data.agentStates[1:] + + def getGhostState( self, agentIndex ): + if agentIndex == 0 or agentIndex >= self.getNumAgents(): + raise Exception("Invalid index passed to getGhostState") + return self.data.agentStates[agentIndex] + + def getGhostPosition( self, agentIndex ): + if agentIndex == 0: + raise Exception("Pacman's index passed to getGhostPosition") + return self.data.agentStates[agentIndex].getPosition() + + def getGhostPositions(self): + return [s.getPosition() for s in self.getGhostStates()] + + def getNumAgents( self ): + return len( self.data.agentStates ) + + def getScore( self ): + return float(self.data.score) + + def getCapsules(self): + """ + Returns a list of positions (x,y) of the remaining capsules. + """ + return self.data.capsules + + def getNumFood( self ): + return self.data.food.count() + + def getFood(self): + """ + Returns a Grid of boolean food indicator variables. + + Grids can be accessed via list notation, so to check + if there is food at (x,y), just call + + currentFood = state.getFood() + if currentFood[x][y] == True: ... + """ + return self.data.food + + def getWalls(self): + """ + Returns a Grid of boolean wall indicator variables. + + Grids can be accessed via list notation, so to check + if there is a wall at (x,y), just call + + walls = state.getWalls() + if walls[x][y] == True: ... + """ + return self.data.layout.walls + + def hasFood(self, x, y): + return self.data.food[x][y] + + def hasWall(self, x, y): + return self.data.layout.walls[x][y] + + def isLose( self ): + return self.data._lose + + def isWin( self ): + return self.data._win + + ############################################# + # Helper methods: # + # You shouldn't need to call these directly # + ############################################# + + def __init__( self, prevState = None ): + """ + Generates a new state by copying information from its predecessor. + """ + if prevState != None: # Initial state + self.data = GameStateData(prevState.data) + else: + self.data = GameStateData() + + def deepCopy( self ): + state = GameState( self ) + state.data = self.data.deepCopy() + return state + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + return hasattr(other, 'data') and self.data == other.data + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + return hash( self.data ) + + def __str__( self ): + + return str(self.data) + + def initialize( self, layout, numGhostAgents=1000 ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.data.initialize(layout, numGhostAgents) + +############################################################################ +# THE HIDDEN SECRETS OF PACMAN # +# # +# You shouldn't need to look through the code in this section of the file. # +############################################################################ + +SCARED_TIME = 40 # Moves ghosts are scared +COLLISION_TOLERANCE = 0.7 # How close ghosts must be to Pacman to kill +TIME_PENALTY = 1 # Number of points lost each round + +class ClassicGameRules: + """ + These game rules manage the control flow of a game, deciding when + and how the game starts and ends. + """ + def __init__(self, timeout=30): + self.timeout = timeout + + def newGame( self, layout, pacmanAgent, ghostAgents, display, quiet = False, catchExceptions=False): + agents = [pacmanAgent] + ghostAgents[:layout.getNumGhosts()] + initState = GameState() + initState.initialize( layout, len(ghostAgents) ) + game = Game(agents, display, self, catchExceptions=catchExceptions) + game.state = initState + self.initialState = initState.deepCopy() + self.quiet = quiet + return game + + def process(self, state, game): + """ + Checks to see whether it is time to end the game. + """ + if state.isWin(): self.win(state, game) + if state.isLose(): self.lose(state, game) + + def win( self, state, game ): + if not self.quiet: print("Pacman emerges victorious! Score: %d" % state.data.score) + game.gameOver = True + + def lose( self, state, game ): + if not self.quiet: print("Pacman died! Score: %d" % state.data.score) + game.gameOver = True + + def getProgress(self, game): + return float(game.state.getNumFood()) / self.initialState.getNumFood() + + def agentCrash(self, game, agentIndex): + if agentIndex == 0: + print("Pacman crashed") + else: + print("A ghost crashed") + + def getMaxTotalTime(self, agentIndex): + return self.timeout + + def getMaxStartupTime(self, agentIndex): + return self.timeout + + def getMoveWarningTime(self, agentIndex): + return self.timeout + + def getMoveTimeout(self, agentIndex): + return self.timeout + + def getMaxTimeWarnings(self, agentIndex): + return 0 + +class PacmanRules: + """ + These functions govern how pacman interacts with his environment under + the classic game rules. + """ + PACMAN_SPEED=1 + + def getLegalActions( state ): + """ + Returns a list of possible actions. + """ + return Actions.getPossibleActions( state.getPacmanState().configuration, state.data.layout.walls ) + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action ): + """ + Edits the state to reflect the results of the action. + """ + legal = PacmanRules.getLegalActions( state ) + if action not in legal: + raise Exception("Illegal action " + str(action)) + + pacmanState = state.data.agentStates[0] + + # Update Configuration + vector = Actions.directionToVector( action, PacmanRules.PACMAN_SPEED ) + pacmanState.configuration = pacmanState.configuration.generateSuccessor( vector ) + + # Eat + next = pacmanState.configuration.getPosition() + nearest = nearestPoint( next ) + if manhattanDistance( nearest, next ) <= 0.5 : + # Remove food + PacmanRules.consume( nearest, state ) + applyAction = staticmethod( applyAction ) + + def consume( position, state ): + x,y = position + # Eat food + if state.data.food[x][y]: + state.data.scoreChange += 10 + state.data.food = state.data.food.copy() + state.data.food[x][y] = False + state.data._foodEaten = position + # TODO: cache numFood? + numFood = state.getNumFood() + if numFood == 0 and not state.data._lose: + state.data.scoreChange += 500 + state.data._win = True + # Eat capsule + if( position in state.getCapsules() ): + state.data.capsules.remove( position ) + state.data._capsuleEaten = position + # Reset all ghosts' scared timers + for index in range( 1, len( state.data.agentStates ) ): + state.data.agentStates[index].scaredTimer = SCARED_TIME + consume = staticmethod( consume ) + +class GhostRules: + """ + These functions dictate how ghosts interact with their environment. + """ + GHOST_SPEED=1.0 + def getLegalActions( state, ghostIndex ): + """ + Ghosts cannot stop, and cannot turn around unless they + reach a dead end, but can turn 90 degrees at intersections. + """ + conf = state.getGhostState( ghostIndex ).configuration + possibleActions = Actions.getPossibleActions( conf, state.data.layout.walls ) + reverse = Actions.reverseDirection( conf.direction ) + if Directions.STOP in possibleActions: + possibleActions.remove( Directions.STOP ) + if reverse in possibleActions and len( possibleActions ) > 1: + possibleActions.remove( reverse ) + return possibleActions + getLegalActions = staticmethod( getLegalActions ) + + def applyAction( state, action, ghostIndex): + + legal = GhostRules.getLegalActions( state, ghostIndex ) + if action not in legal: + raise Exception("Illegal ghost action " + str(action)) + + ghostState = state.data.agentStates[ghostIndex] + speed = GhostRules.GHOST_SPEED + if ghostState.scaredTimer > 0: speed /= 2.0 + vector = Actions.directionToVector( action, speed ) + ghostState.configuration = ghostState.configuration.generateSuccessor( vector ) + applyAction = staticmethod( applyAction ) + + def decrementTimer( ghostState): + timer = ghostState.scaredTimer + if timer == 1: + ghostState.configuration.pos = nearestPoint( ghostState.configuration.pos ) + ghostState.scaredTimer = max( 0, timer - 1 ) + decrementTimer = staticmethod( decrementTimer ) + + def checkDeath( state, agentIndex): + pacmanPosition = state.getPacmanPosition() + if agentIndex == 0: # Pacman just moved; Anyone can kill him + for index in range( 1, len( state.data.agentStates ) ): + ghostState = state.data.agentStates[index] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, index ) + else: + ghostState = state.data.agentStates[agentIndex] + ghostPosition = ghostState.configuration.getPosition() + if GhostRules.canKill( pacmanPosition, ghostPosition ): + GhostRules.collide( state, ghostState, agentIndex ) + checkDeath = staticmethod( checkDeath ) + + def collide( state, ghostState, agentIndex): + if ghostState.scaredTimer > 0: + state.data.scoreChange += 200 + GhostRules.placeGhost(state, ghostState) + ghostState.scaredTimer = 0 + # Added for first-person + state.data._eaten[agentIndex] = True + else: + if not state.data._win: + state.data.scoreChange -= 500 + state.data._lose = True + collide = staticmethod( collide ) + + def canKill( pacmanPosition, ghostPosition ): + return manhattanDistance( ghostPosition, pacmanPosition ) <= COLLISION_TOLERANCE + canKill = staticmethod( canKill ) + + def placeGhost(state, ghostState): + ghostState.configuration = ghostState.start + placeGhost = staticmethod( placeGhost ) + +############################# +# FRAMEWORK TO START A GAME # +############################# + +def default(str): + return str + ' [Default: %default]' + +def parseAgentArgs(str): + if str == None: return {} + pieces = str.split(',') + opts = {} + for p in pieces: + if '=' in p: + key, val = p.split('=') + else: + key,val = p, 1 + opts[key] = val + return opts + +def readCommand( argv ): + """ + Processes the command used to run pacman from the command line. + """ + from optparse import OptionParser + usageStr = """ + USAGE: python pacman.py + EXAMPLES: (1) python pacman.py + - starts an interactive game + (2) python pacman.py --layout smallClassic --zoom 2 + OR python pacman.py -l smallClassic -z 2 + - starts an interactive game on a smaller board, zoomed in + """ + parser = OptionParser(usageStr) + + parser.add_option('-n', '--numGames', dest='numGames', type='int', + help=default('the number of GAMES to play'), metavar='GAMES', default=1) + parser.add_option('-l', '--layout', dest='layout', + help=default('the LAYOUT_FILE from which to load the map layout'), + metavar='LAYOUT_FILE', default='mediumClassic') + parser.add_option('-p', '--pacman', dest='pacman', + help=default('the agent TYPE in the pacmanAgents module to use'), + metavar='TYPE', default='KeyboardAgent') + parser.add_option('-t', '--textGraphics', action='store_true', dest='textGraphics', + help='Display output as text only', default=False) + parser.add_option('-q', '--quietTextGraphics', action='store_true', dest='quietGraphics', + help='Generate minimal output and no graphics', default=False) + parser.add_option('-g', '--ghosts', dest='ghost', + help=default('the ghost agent TYPE in the ghostAgents module to use'), + metavar = 'TYPE', default='RandomGhost') + parser.add_option('-k', '--numghosts', type='int', dest='numGhosts', + help=default('The maximum number of ghosts to use'), default=4) + parser.add_option('-z', '--zoom', type='float', dest='zoom', + help=default('Zoom the size of the graphics window'), default=1.0) + parser.add_option('-f', '--fixRandomSeed', action='store_true', dest='fixRandomSeed', + help='Fixes the random seed to always play the same game', default=False) + parser.add_option('-r', '--recordActions', action='store_true', dest='record', + help='Writes game histories to a file (named by the time they were played)', default=False) + parser.add_option('--replay', dest='gameToReplay', + help='A recorded game file (pickle) to replay', default=None) + parser.add_option('-a','--agentArgs',dest='agentArgs', + help='Comma separated values sent to agent. e.g. "opt1=val1,opt2,opt3=val3"') + parser.add_option('-x', '--numTraining', dest='numTraining', type='int', + help=default('How many episodes are training (suppresses output)'), default=0) + parser.add_option('--frameTime', dest='frameTime', type='float', + help=default('Time to delay between frames; <0 means keyboard'), default=0.1) + parser.add_option('-c', '--catchExceptions', action='store_true', dest='catchExceptions', + help='Turns on exception handling and timeouts during games', default=False) + parser.add_option('--timeout', dest='timeout', type='int', + help=default('Maximum length of time an agent can spend computing in a single game'), default=30) + + options, otherjunk = parser.parse_args(argv) + if len(otherjunk) != 0: + raise Exception('Command line input not understood: ' + str(otherjunk)) + args = dict() + + # Fix the random seed + if options.fixRandomSeed: random.seed('cs188') + + # Choose a layout + args['layout'] = layout.getLayout( options.layout ) + if args['layout'] == None: raise Exception("The layout " + options.layout + " cannot be found") + + # Choose a Pacman agent + noKeyboard = options.gameToReplay == None and (options.textGraphics or options.quietGraphics) + pacmanType = loadAgent(options.pacman, noKeyboard) + agentOpts = parseAgentArgs(options.agentArgs) + if options.numTraining > 0: + args['numTraining'] = options.numTraining + if 'numTraining' not in agentOpts: agentOpts['numTraining'] = options.numTraining + pacman = pacmanType(**agentOpts) # Instantiate Pacman with agentArgs + args['pacman'] = pacman + + # Don't display training games + if 'numTrain' in agentOpts: + options.numQuiet = int(agentOpts['numTrain']) + options.numIgnore = int(agentOpts['numTrain']) + + # Choose a ghost agent + ghostType = loadAgent(options.ghost, noKeyboard) + args['ghosts'] = [ghostType( i+1 ) for i in range( options.numGhosts )] + + # Choose a display format + if options.quietGraphics: + import textDisplay + args['display'] = textDisplay.NullGraphics() + elif options.textGraphics: + import textDisplay + textDisplay.SLEEP_TIME = options.frameTime + args['display'] = textDisplay.PacmanGraphics() + else: + import graphicsDisplay + args['display'] = graphicsDisplay.PacmanGraphics(options.zoom, frameTime = options.frameTime) + args['numGames'] = options.numGames + args['record'] = options.record + args['catchExceptions'] = options.catchExceptions + args['timeout'] = options.timeout + + # Special case: recorded games don't use the runGames method or args structure + if options.gameToReplay != None: + print('Replaying recorded game %s.' % options.gameToReplay) + import pickle + f = open(options.gameToReplay, 'rb') + try: recorded = pickle.load(f) + finally: f.close() + recorded['display'] = args['display'] + replayGame(**recorded) + sys.exit(0) + + return args + +def loadAgent(pacman, nographics): + # Looks through all pythonPath Directories for the right module, + pythonPathStr = os.path.expandvars("$PYTHONPATH") + if pythonPathStr.find(';') == -1: + pythonPathDirs = pythonPathStr.split(':') + else: + pythonPathDirs = pythonPathStr.split(';') + pythonPathDirs.append('.') + + for moduleDir in pythonPathDirs: + if not os.path.isdir(moduleDir): continue + moduleNames = [f for f in os.listdir(moduleDir) if f.endswith('gents.py')] + for modulename in moduleNames: + try: + module = __import__(modulename[:-3]) + except ImportError: + continue + if pacman in dir(module): + if nographics and modulename == 'keyboardAgents.py': + raise Exception('Using the keyboard requires graphics (not text display)') + return getattr(module, pacman) + raise Exception('The agent ' + pacman + ' is not specified in any *Agents.py.') + +def replayGame( layout, actions, display ): + import pacmanAgents, ghostAgents + rules = ClassicGameRules() + agents = [pacmanAgents.GreedyAgent()] + [ghostAgents.RandomGhost(i+1) for i in range(layout.getNumGhosts())] + game = rules.newGame( layout, agents[0], agents[1:], display ) + state = game.state + display.initialize(state.data) + + for action in actions: + # Execute the action + state = state.generateSuccessor( *action ) + # Change the display + display.update( state.data ) + # Allow for game specific conditions (winning, losing, etc.) + rules.process(state, game) + + display.finish() + +def runGames( layout, pacman, ghosts, display, numGames, record, numTraining = 0, catchExceptions=False, timeout=30 ): + import __main__ + __main__.__dict__['_display'] = display + + rules = ClassicGameRules(timeout) + games = [] + + for i in range( numGames ): + beQuiet = i < numTraining + if beQuiet: + # Suppress output and graphics + import textDisplay + gameDisplay = textDisplay.NullGraphics() + rules.quiet = True + else: + gameDisplay = display + rules.quiet = False + game = rules.newGame( layout, pacman, ghosts, gameDisplay, beQuiet, catchExceptions) + game.run() + if not beQuiet: games.append(game) + + if record: + import time, pickle + fname = ('recorded-game-%d' % (i + 1)) + '-'.join([str(t) for t in time.localtime()[1:6]]) + f = open(fname, 'wb') + components = {'layout': layout, 'actions': game.moveHistory} + pickle.dump(components, f) + f.close() + + if (numGames-numTraining) > 0: + scores = [game.state.getScore() for game in games] + wins = [game.state.isWin() for game in games] + winRate = wins.count(True)/ float(len(wins)) + print('Average Score:', sum(scores) / float(len(scores))) + print('Scores: ', ', '.join([str(score) for score in scores])) + print('Win Rate: %d/%d (%.2f)' % (wins.count(True), len(wins), winRate)) + print('Record: ', ', '.join([ ['Loss', 'Win'][int(w)] for w in wins])) + + return games + +if __name__ == '__main__': + """ + The main function called when pacman.py is run + from the command line: + + > python pacman.py + + See the usage string for more details. + + > python pacman.py --help + """ + args = readCommand( sys.argv[1:] ) # Get game components based on input + runGames( **args ) + + # import cProfile + # cProfile.run("runGames( **args )") + pass diff --git a/school/cpsc481/project1/src/pacmanAgents.py b/school/cpsc481/project1/src/pacmanAgents.py new file mode 100644 index 0000000..ae97634 --- /dev/null +++ b/school/cpsc481/project1/src/pacmanAgents.py @@ -0,0 +1,52 @@ +# pacmanAgents.py +# --------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from pacman import Directions +from game import Agent +import random +import game +import util + +class LeftTurnAgent(game.Agent): + "An agent that turns left at every opportunity" + + def getAction(self, state): + legal = state.getLegalPacmanActions() + current = state.getPacmanState().configuration.direction + if current == Directions.STOP: current = Directions.NORTH + left = Directions.LEFT[current] + if left in legal: return left + if current in legal: return current + if Directions.RIGHT[current] in legal: return Directions.RIGHT[current] + if Directions.LEFT[left] in legal: return Directions.LEFT[left] + return Directions.STOP + +class GreedyAgent(Agent): + def __init__(self, evalFn="scoreEvaluation"): + self.evaluationFunction = util.lookup(evalFn, globals()) + assert self.evaluationFunction != None + + def getAction(self, state): + # Generate candidate actions + legal = state.getLegalPacmanActions() + if Directions.STOP in legal: legal.remove(Directions.STOP) + + successors = [(state.generateSuccessor(0, action), action) for action in legal] + scored = [(self.evaluationFunction(state), action) for state, action in successors] + bestScore = max(scored)[0] + bestActions = [pair[1] for pair in scored if pair[0] == bestScore] + return random.choice(bestActions) + +def scoreEvaluation(state): + return state.getScore() diff --git a/school/cpsc481/project1/src/projectParams.py b/school/cpsc481/project1/src/projectParams.py new file mode 100644 index 0000000..dc3e9d1 --- /dev/null +++ b/school/cpsc481/project1/src/projectParams.py @@ -0,0 +1,18 @@ +# projectParams.py +# ---------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +STUDENT_CODE_DEFAULT = 'searchAgents.py,search.py' +PROJECT_TEST_CLASSES = 'searchTestClasses.py' +PROJECT_NAME = 'Project 1: Search' +BONUS_PIC = False diff --git a/school/cpsc481/project1/src/search.py b/school/cpsc481/project1/src/search.py new file mode 100644 index 0000000..f6a1fd0 --- /dev/null +++ b/school/cpsc481/project1/src/search.py @@ -0,0 +1,164 @@ +# search.py +# --------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +In search.py, you will implement generic search algorithms which are called by +Pacman agents (in searchAgents.py). +""" + +import util +# user made functions + + +class SearchProblem: + """ + This class outlines the structure of a search problem, but doesn't implement + any of the methods (in object-oriented terminology: an abstract class). + + You do not need to change anything in this class, ever. + """ + + def getStartState(self): + """ + Returns the start state for the search problem. + """ + util.raiseNotDefined() + + def isGoalState(self, state): + """ + state: Search state + + Returns True if and only if the state is a valid goal state. + """ + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + state: Search state + + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' is + the incremental cost of expanding to that successor. + """ + util.raiseNotDefined() + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. + The sequence must be composed of legal moves. + """ + util.raiseNotDefined() + + +def tinyMazeSearch(problem): + """ + Returns a sequence of moves that solves tinyMaze. For any other maze, the + sequence of moves will be incorrect, so only use this for tinyMaze. + """ + from game import Directions + s = Directions.SOUTH + w = Directions.WEST + return [s, s, w, s, w, w, s, w] + +def baseSearchFunction(problem, data_structure): + """ + Since the implementation of a Depth First Search and Breadth First Search + are nearly identical, we can simply switch the date structure based on the + order we recieve our nodes; i.e. change the direction we pop, push nodes. + """ + # The path that takes us from the start node to the destination + answer_path = [] + + # Set the open_nodes data structure as either a Stack (DFS) or Queue (BFS) + open_nodes = data_structure() + + # Add the starting state and the empty list to our open nodes + open_nodes.push((problem.getStartState(), answer_path)) + + # Initialize closed nodes to contain nothing + closed_nodes = [] + + # While there is at least one item in our open_nodes + while(not open_nodes.isEmpty()): + + # Assign current_node and answer_path to an element from one side of the list + current_node, answer_path = open_nodes.pop() + + # If our current node isn't closed + if(current_node not in closed_nodes): + + # Add the current node to the set of closed nodes + closed_nodes.append(current_node) + + # If we're at the destination + if(problem.isGoalState(current_node)): + # Return the path we used to get here + return answer_path + + # Set successors equal to the children of the current node + successors = problem.getSuccessors(current_node) + + # For each successor + for state in successors: + + #Set the node, path equal to the node and path of the state (We're not concerned about the cost) + node, path, _ = state + + #If the current node isn't in the set of closed nodes + if(node not in closed_nodes): + + # Add the node and path to the set of open nodes + open_nodes.push((node, answer_path + [path])) + + #Return the 'best' answer_path we could find; If we're returning here, we didn't find the destination + return answer_path + +def depthFirstSearch(problem): + """Search the deepest nodes in the search tree first.""" + + #Use a stack in order to perform a Depth First Search + return baseSearchFunction(problem, util.Stack) + +def breadthFirstSearch(problem): + """Search the shallowest nodes in the search tree first.""" + + #Use a Queue in order to perform a Breadth First Search + return baseSearchFunction(problem, util.Queue) + +def uniformCostSearch(problem): + """Search the node of least total cost first.""" + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +def nullHeuristic(state, problem=None): + """ + A heuristic function estimates the cost from the current state to the nearest + goal in the provided SearchProblem. This heuristic is trivial. + """ + return 0 + +def aStarSearch(problem, heuristic=nullHeuristic): + """Search the node that has the lowest combined cost and heuristic first.""" + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + + +# Abbreviations +bfs = breadthFirstSearch +dfs = depthFirstSearch +astar = aStarSearch +ucs = uniformCostSearch diff --git a/school/cpsc481/project1/src/search.token b/school/cpsc481/project1/src/search.token new file mode 100644 index 0000000..b6c0fe3 --- /dev/null +++ b/school/cpsc481/project1/src/search.token @@ -0,0 +1 @@ +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 diff --git a/school/cpsc481/project1/src/searchAgents.py b/school/cpsc481/project1/src/searchAgents.py new file mode 100644 index 0000000..3a91623 --- /dev/null +++ b/school/cpsc481/project1/src/searchAgents.py @@ -0,0 +1,542 @@ +# searchAgents.py +# --------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +""" +This file contains all of the agents that can be selected to control Pacman. To +select an agent, use the '-p' option when running pacman.py. Arguments can be +passed to your agent using '-a'. For example, to load a SearchAgent that uses +depth first search (dfs), run the following command: + +> python pacman.py -p SearchAgent -a fn=depthFirstSearch + +Commands to invoke other search strategies can be found in the project +description. + +Please only change the parts of the file you are asked to. Look for the lines +that say + +"*** YOUR CODE HERE ***" + +The parts you fill in start about 3/4 of the way down. Follow the project +description for details. + +Good luck and happy searching! +""" + +from game import Directions +from game import Agent +from game import Actions +import util +import time +import search + +class GoWestAgent(Agent): + "An agent that goes West until it can't." + + def getAction(self, state): + "The agent receives a GameState (defined in pacman.py)." + if Directions.WEST in state.getLegalPacmanActions(): + return Directions.WEST + else: + return Directions.STOP + +####################################################### +# This portion is written for you, but will only work # +# after you fill in parts of search.py # +####################################################### + +class SearchAgent(Agent): + """ + This very general search agent finds a path using a supplied search + algorithm for a supplied search problem, then returns actions to follow that + path. + + As a default, this agent runs DFS on a PositionSearchProblem to find + location (1,1) + + Options for fn include: + depthFirstSearch or dfs + breadthFirstSearch or bfs + + + Note: You should NOT change any code in SearchAgent + """ + + def __init__(self, fn='depthFirstSearch', prob='PositionSearchProblem', heuristic='nullHeuristic'): + # Warning: some advanced Python magic is employed below to find the right functions and problems + + # Get the search function from the name and heuristic + if fn not in dir(search): + raise AttributeError(fn + ' is not a search function in search.py.') + func = getattr(search, fn) + if 'heuristic' not in func.__code__.co_varnames: + print('[SearchAgent] using function ' + fn) + self.searchFunction = func + else: + if heuristic in globals().keys(): + heur = globals()[heuristic] + elif heuristic in dir(search): + heur = getattr(search, heuristic) + else: + raise AttributeError(heuristic + ' is not a function in searchAgents.py or search.py.') + print('[SearchAgent] using function %s and heuristic %s' % (fn, heuristic)) + # Note: this bit of Python trickery combines the search algorithm and the heuristic + self.searchFunction = lambda x: func(x, heuristic=heur) + + # Get the search problem type from the name + if prob not in globals().keys() or not prob.endswith('Problem'): + raise AttributeError(prob + ' is not a search problem type in SearchAgents.py.') + self.searchType = globals()[prob] + print('[SearchAgent] using problem type ' + prob) + + def registerInitialState(self, state): + """ + This is the first time that the agent sees the layout of the game + board. Here, we choose a path to the goal. In this phase, the agent + should compute the path to the goal and store it in a local variable. + All of the work is done in this method! + + state: a GameState object (pacman.py) + """ + if self.searchFunction == None: raise Exception("No search function provided for SearchAgent") + starttime = time.time() + problem = self.searchType(state) # Makes a new search problem + self.actions = self.searchFunction(problem) # Find a path + totalCost = problem.getCostOfActions(self.actions) + print('Path found with total cost of %d in %.1f seconds' % (totalCost, time.time() - starttime)) + if '_expanded' in dir(problem): print('Search nodes expanded: %d' % problem._expanded) + + def getAction(self, state): + """ + Returns the next action in the path chosen earlier (in + registerInitialState). Return Directions.STOP if there is no further + action to take. + + state: a GameState object (pacman.py) + """ + if 'actionIndex' not in dir(self): self.actionIndex = 0 + i = self.actionIndex + self.actionIndex += 1 + if i < len(self.actions): + return self.actions[i] + else: + return Directions.STOP + +class PositionSearchProblem(search.SearchProblem): + """ + A search problem defines the state space, start state, goal test, successor + function and cost function. This search problem can be used to find paths + to a particular point on the pacman board. + + The state space consists of (x,y) positions in a pacman game. + + Note: this search problem is fully specified; you should NOT change it. + """ + + def __init__(self, gameState, costFn = lambda x: 1, goal=(1,1), start=None, warn=True, visualize=True): + """ + Stores the start and goal. + + gameState: A GameState object (pacman.py) + costFn: A function from a search state (tuple) to a non-negative number + goal: A position in the gameState + """ + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + if start != None: self.startState = start + self.goal = goal + self.costFn = costFn + self.visualize = visualize + if warn and (gameState.getNumFood() != 1 or not gameState.hasFood(*goal)): + print('Warning: this does not look like a regular search maze') + + # For display purposes + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def getStartState(self): + return self.startState + + def isGoalState(self, state): + isGoal = state == self.goal + + # For display purposes only + if isGoal and self.visualize: + self._visitedlist.append(state) + import __main__ + if '_display' in dir(__main__): + if 'drawExpandedCells' in dir(__main__._display): #@UndefinedVariable + __main__._display.drawExpandedCells(self._visitedlist) #@UndefinedVariable + + return isGoal + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, + (successor, action, stepCost), where 'successor' is a + successor to the current state, 'action' is the action + required to get there, and 'stepCost' is the incremental + cost of expanding to that successor + """ + + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state + dx, dy = Actions.directionToVector(action) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextState = (nextx, nexty) + cost = self.costFn(nextState) + successors.append( ( nextState, action, cost) ) + + # Bookkeeping for display purposes + self._expanded += 1 # DO NOT CHANGE + if state not in self._visited: + self._visited[state] = True + self._visitedlist.append(state) + + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. + """ + if actions == None: return 999999 + x,y= self.getStartState() + cost = 0 + for action in actions: + # Check figure out the next state and see whether its' legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + cost += self.costFn((x,y)) + return cost + +class StayEastSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the West side of the board. + + The cost function for stepping into a position (x,y) is 1/2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: .5 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn, (1, 1), None, False) + +class StayWestSearchAgent(SearchAgent): + """ + An agent for position search with a cost function that penalizes being in + positions on the East side of the board. + + The cost function for stepping into a position (x,y) is 2^x. + """ + def __init__(self): + self.searchFunction = search.uniformCostSearch + costFn = lambda pos: 2 ** pos[0] + self.searchType = lambda state: PositionSearchProblem(state, costFn) + +def manhattanHeuristic(position, problem, info={}): + "The Manhattan distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return abs(xy1[0] - xy2[0]) + abs(xy1[1] - xy2[1]) + +def euclideanHeuristic(position, problem, info={}): + "The Euclidean distance heuristic for a PositionSearchProblem" + xy1 = position + xy2 = problem.goal + return ( (xy1[0] - xy2[0]) ** 2 + (xy1[1] - xy2[1]) ** 2 ) ** 0.5 + +##################################################### +# This portion is incomplete. Time to write code! # +##################################################### + +class CornersProblem(search.SearchProblem): + """ + This search problem finds paths through all four corners of a layout. + + You must select a suitable state space and successor function + """ + + def __init__(self, startingGameState): + """ + Stores the walls, pacman's starting position and corners. + """ + self.walls = startingGameState.getWalls() + self.startingPosition = startingGameState.getPacmanPosition() + top, right = self.walls.height-2, self.walls.width-2 + self.corners = ((1,1), (1,top), (right, 1), (right, top)) + for corner in self.corners: + if not startingGameState.hasFood(*corner): + print('Warning: no food in corner ' + str(corner)) + self._expanded = 0 # DO NOT CHANGE; Number of search nodes expanded + # Please add any code here which you would like to use + # in initializing the problem + "*** YOUR CODE HERE ***" + + def getStartState(self): + """ + Returns the start state (in your state space, not the full Pacman state + space) + """ + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + + def isGoalState(self, state): + """ + Returns whether this search state is a goal state of the problem. + """ + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + + def getSuccessors(self, state): + """ + Returns successor states, the actions they require, and a cost of 1. + + As noted in search.py: + For a given state, this should return a list of triples, (successor, + action, stepCost), where 'successor' is a successor to the current + state, 'action' is the action required to get there, and 'stepCost' + is the incremental cost of expanding to that successor + """ + + successors = [] + for action in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + # Add a successor state to the successor list if the action is legal + # Here's a code snippet for figuring out whether a new position hits a wall: + # x,y = currentPosition + # dx, dy = Actions.directionToVector(action) + # nextx, nexty = int(x + dx), int(y + dy) + # hitsWall = self.walls[nextx][nexty] + + "*** YOUR CODE HERE ***" + + self._expanded += 1 # DO NOT CHANGE + return successors + + def getCostOfActions(self, actions): + """ + Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999. This is implemented for you. + """ + if actions == None: return 999999 + x,y= self.startingPosition + for action in actions: + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: return 999999 + return len(actions) + + +def cornersHeuristic(state, problem): + """ + A heuristic for the CornersProblem that you defined. + + state: The current search state + (a data structure you chose in your search problem) + + problem: The CornersProblem instance for this layout. + + This function should always return a number that is a lower bound on the + shortest path from the state to a goal of the problem; i.e. it should be + admissible (as well as consistent). + """ + corners = problem.corners # These are the corner coordinates + walls = problem.walls # These are the walls of the maze, as a Grid (game.py) + + "*** YOUR CODE HERE ***" + return 0 # Default to trivial solution + +class AStarCornersAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, cornersHeuristic) + self.searchType = CornersProblem + +class FoodSearchProblem: + """ + A search problem associated with finding the a path that collects all of the + food (dots) in a Pacman game. + + A search state in this problem is a tuple ( pacmanPosition, foodGrid ) where + pacmanPosition: a tuple (x,y) of integers specifying Pacman's position + foodGrid: a Grid (see game.py) of either True or False, specifying remaining food + """ + def __init__(self, startingGameState): + self.start = (startingGameState.getPacmanPosition(), startingGameState.getFood()) + self.walls = startingGameState.getWalls() + self.startingGameState = startingGameState + self._expanded = 0 # DO NOT CHANGE + self.heuristicInfo = {} # A dictionary for the heuristic to store information + + def getStartState(self): + return self.start + + def isGoalState(self, state): + return state[1].count() == 0 + + def getSuccessors(self, state): + "Returns successor states, the actions they require, and a cost of 1." + successors = [] + self._expanded += 1 # DO NOT CHANGE + for direction in [Directions.NORTH, Directions.SOUTH, Directions.EAST, Directions.WEST]: + x,y = state[0] + dx, dy = Actions.directionToVector(direction) + nextx, nexty = int(x + dx), int(y + dy) + if not self.walls[nextx][nexty]: + nextFood = state[1].copy() + nextFood[nextx][nexty] = False + successors.append( ( ((nextx, nexty), nextFood), direction, 1) ) + return successors + + def getCostOfActions(self, actions): + """Returns the cost of a particular sequence of actions. If those actions + include an illegal move, return 999999""" + x,y= self.getStartState()[0] + cost = 0 + for action in actions: + # figure out the next state and see whether it's legal + dx, dy = Actions.directionToVector(action) + x, y = int(x + dx), int(y + dy) + if self.walls[x][y]: + return 999999 + cost += 1 + return cost + +class AStarFoodSearchAgent(SearchAgent): + "A SearchAgent for FoodSearchProblem using A* and your foodHeuristic" + def __init__(self): + self.searchFunction = lambda prob: search.aStarSearch(prob, foodHeuristic) + self.searchType = FoodSearchProblem + +def foodHeuristic(state, problem): + """ + Your heuristic for the FoodSearchProblem goes here. + + This heuristic must be consistent to ensure correctness. First, try to come + up with an admissible heuristic; almost all admissible heuristics will be + consistent as well. + + If using A* ever finds a solution that is worse uniform cost search finds, + your heuristic is *not* consistent, and probably not admissible! On the + other hand, inadmissible or inconsistent heuristics may find optimal + solutions, so be careful. + + The state is a tuple ( pacmanPosition, foodGrid ) where foodGrid is a Grid + (see game.py) of either True or False. You can call foodGrid.asList() to get + a list of food coordinates instead. + + If you want access to info like walls, capsules, etc., you can query the + problem. For example, problem.walls gives you a Grid of where the walls + are. + + If you want to *store* information to be reused in other calls to the + heuristic, there is a dictionary called problem.heuristicInfo that you can + use. For example, if you only want to count the walls once and store that + value, try: problem.heuristicInfo['wallCount'] = problem.walls.count() + Subsequent calls to this heuristic can access + problem.heuristicInfo['wallCount'] + """ + position, foodGrid = state + "*** YOUR CODE HERE ***" + return 0 + +class ClosestDotSearchAgent(SearchAgent): + "Search for all food using a sequence of searches" + def registerInitialState(self, state): + self.actions = [] + currentState = state + while(currentState.getFood().count() > 0): + nextPathSegment = self.findPathToClosestDot(currentState) # The missing piece + self.actions += nextPathSegment + for action in nextPathSegment: + legal = currentState.getLegalActions() + if action not in legal: + t = (str(action), str(currentState)) + raise Exception('findPathToClosestDot returned an illegal move: %s!\n%s' % t) + currentState = currentState.generateSuccessor(0, action) + self.actionIndex = 0 + print('Path found with cost %d.' % len(self.actions)) + + def findPathToClosestDot(self, gameState): + """ + Returns a path (a list of actions) to the closest dot, starting from + gameState. + """ + # Here are some useful elements of the startState + startPosition = gameState.getPacmanPosition() + food = gameState.getFood() + walls = gameState.getWalls() + problem = AnyFoodSearchProblem(gameState) + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +class AnyFoodSearchProblem(PositionSearchProblem): + """ + A search problem for finding a path to any food. + + This search problem is just like the PositionSearchProblem, but has a + different goal test, which you need to fill in below. The state space and + successor function do not need to be changed. + + The class definition above, AnyFoodSearchProblem(PositionSearchProblem), + inherits the methods of the PositionSearchProblem. + + You can use this search problem to help you fill in the findPathToClosestDot + method. + """ + + def __init__(self, gameState): + "Stores information from the gameState. You don't need to change this." + # Store the food for later reference + self.food = gameState.getFood() + + # Store info for the PositionSearchProblem (no need to change this) + self.walls = gameState.getWalls() + self.startState = gameState.getPacmanPosition() + self.costFn = lambda x: 1 + self._visited, self._visitedlist, self._expanded = {}, [], 0 # DO NOT CHANGE + + def isGoalState(self, state): + """ + The state is Pacman's position. Fill this in with a goal test that will + complete the problem definition. + """ + x,y = state + + "*** YOUR CODE HERE ***" + util.raiseNotDefined() + +def mazeDistance(point1, point2, gameState): + """ + Returns the maze distance between any two points, using the search functions + you have already built. The gameState can be any game state -- Pacman's + position in that state is ignored. + + Example usage: mazeDistance( (2,4), (5,6), gameState) + + This might be a useful helper function for your ApproximateSearchAgent. + """ + x1, y1 = point1 + x2, y2 = point2 + walls = gameState.getWalls() + assert not walls[x1][y1], 'point1 is a wall: ' + str(point1) + assert not walls[x2][y2], 'point2 is a wall: ' + str(point2) + prob = PositionSearchProblem(gameState, start=point1, goal=point2, warn=False, visualize=False) + return len(search.bfs(prob)) diff --git a/school/cpsc481/project1/src/searchTestClasses.py b/school/cpsc481/project1/src/searchTestClasses.py new file mode 100644 index 0000000..f2a01af --- /dev/null +++ b/school/cpsc481/project1/src/searchTestClasses.py @@ -0,0 +1,823 @@ +# searchTestClasses.py +# -------------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import re +import testClasses +import textwrap + +# import project specific code +import layout +import pacman +from search import SearchProblem + +# helper function for printing solutions in solution files +def wrap_solution(solution): + if type(solution) == type([]): + return '\n'.join(textwrap.wrap(' '.join(solution))) + else: + return str(solution) + + + + +def followAction(state, action, problem): + for successor1, action1, cost1 in problem.getSuccessors(state): + if action == action1: return successor1 + return None + +def followPath(path, problem): + state = problem.getStartState() + states = [state] + for action in path: + state = followAction(state, action, problem) + states.append(state) + return states + +def checkSolution(problem, path): + state = problem.getStartState() + for action in path: + state = followAction(state, action, problem) + return problem.isGoalState(state) + +# Search problem on a plain graph +class GraphSearch(SearchProblem): + + # Read in the state graph; define start/end states, edges and costs + def __init__(self, graph_text): + self.expanded_states = [] + lines = graph_text.split('\n') + r = re.match('start_state:(.*)', lines[0]) + if r == None: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("GraphSearch graph specification start_state not found or incorrect on line 0") + self.start_state = r.group(1).strip() + r = re.match('goal_states:(.*)', lines[1]) + if r == None: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("GraphSearch graph specification goal_states not found or incorrect on line 1") + goals = r.group(1).split() + self.goals = [str.strip(g) for g in goals] + self.successors = {} + all_states = set() + self.orderedSuccessorTuples = [] + for l in lines[2:]: + if len(l.split()) == 3: + start, action, next_state = l.split() + cost = 1 + elif len(l.split()) == 4: + start, action, next_state, cost = l.split() + else: + print("Broken graph:") + print('"""%s"""' % graph_text) + raise Exception("Invalid line in GraphSearch graph specification on line:" + l) + cost = float(cost) + self.orderedSuccessorTuples.append((start, action, next_state, cost)) + all_states.add(start) + all_states.add(next_state) + if start not in self.successors: + self.successors[start] = [] + self.successors[start].append((next_state, action, cost)) + for s in all_states: + if s not in self.successors: + self.successors[s] = [] + + # Get start state + def getStartState(self): + return self.start_state + + # Check if a state is a goal state + def isGoalState(self, state): + return state in self.goals + + # Get all successors of a state + def getSuccessors(self, state): + self.expanded_states.append(state) + return list(self.successors[state]) + + # Calculate total cost of a sequence of actions + def getCostOfActions(self, actions): + total_cost = 0 + state = self.start_state + for a in actions: + successors = self.successors[state] + match = False + for (next_state, action, cost) in successors: + if a == action: + state = next_state + total_cost += cost + match = True + if not match: + print('invalid action sequence') + sys.exit(1) + return total_cost + + # Return a list of all states on which 'getSuccessors' was called + def getExpandedStates(self): + return self.expanded_states + + def __str__(self): + print(self.successors) + edges = ["%s %s %s %s" % t for t in self.orderedSuccessorTuples] + return \ +"""start_state: %s +goal_states: %s +%s""" % (self.start_state, " ".join(self.goals), "\n".join(edges)) + + + +def parseHeuristic(heuristicText): + heuristic = {} + for line in heuristicText.split('\n'): + tokens = line.split() + if len(tokens) != 2: + print("Broken heuristic:") + print('"""%s"""' % heuristicText) + raise Exception("GraphSearch heuristic specification broken at tokens:" + str(tokens)) + state, h = tokens + heuristic[state] = float(h) + + def graphHeuristic(state, problem=None): + if state in heuristic: + return heuristic[state] + else: + import pprint + pp = pprint.PrettyPrinter(indent=4) + print("Heuristic:") + pp.pprint(heuristic) + raise Exception("Graph heuristic called with invalid state: " + str(state)) + + return graphHeuristic + + +class GraphSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(GraphSearchTest, self).__init__(question, testDict) + self.graph_text = testDict['graph'] + self.alg = testDict['algorithm'] + self.diagram = testDict['diagram'] + self.exactExpansionOrder = testDict.get('exactExpansionOrder', 'True').lower() == "true" + if 'heuristic' in testDict: + self.heuristic = parseHeuristic(testDict['heuristic']) + else: + self.heuristic = None + + # Note that the return type of this function is a tripple: + # (solution, expanded states, error message) + def getSolInfo(self, search): + alg = getattr(search, self.alg) + problem = GraphSearch(self.graph_text) + if self.heuristic != None: + solution = alg(problem, self.heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + return solution, problem.getExpandedStates(), None + + # Run student code. If an error message is returned, print error and return false. + # If a good solution is returned, printn the solution and return true; otherwise, + # print both the correct and student's solution and return false. + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded_states = [str.split(solutionDict['expanded_states']), str.split(solutionDict['rev_expanded_states'])] + + solution, expanded_states, error = self.getSolInfo(search) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\t%s' % error) + return False + + if solution in gold_solution and (not self.exactExpansionOrder or expanded_states in gold_expanded_states): + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tsolution:\t\t%s' % solution) + grades.addMessage('\texpanded_states:\t%s' % expanded_states) + return True + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tgraph:') + for line in self.diagram.split('\n'): + grades.addMessage('\t %s' % (line,)) + grades.addMessage('\tstudent solution:\t\t%s' % solution) + grades.addMessage('\tstudent expanded_states:\t%s' % expanded_states) + grades.addMessage('') + grades.addMessage('\tcorrect solution:\t\t%s' % gold_solution[0]) + grades.addMessage('\tcorrect expanded_states:\t%s' % gold_expanded_states[0]) + grades.addMessage('\tcorrect rev_solution:\t\t%s' % gold_solution[1]) + grades.addMessage('\tcorrect rev_expanded_states:\t%s' % gold_expanded_states[1]) + return False + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + + # write forward solution + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: "%s"\n' % ' '.join(solution)) + handle.write('expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # reverse and write backwards solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded_states, error = self.getSolInfo(search) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: "%s"\n' % ' '.join(solution)) + handle.write('rev_expanded_states: "%s"\n' % ' '.join(expanded_states)) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + + +class PacmanSearchTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(PacmanSearchTest, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + self.alg = testDict['algorithm'] + self.layoutName = testDict['layoutName'] + + # TODO: sensible to have defaults like this? + self.leewayFactor = float(testDict.get('leewayFactor', '1')) + self.costFn = eval(testDict.get('costFn', 'None')) + self.searchProblemClassName = testDict.get('searchProblemClass', 'PositionSearchProblem') + self.heuristicName = testDict.get('heuristic', None) + + + def getSolInfo(self, search, searchAgents): + alg = getattr(search, self.alg) + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + + problemClass = getattr(searchAgents, self.searchProblemClassName) + problemOptions = {} + if self.costFn != None: + problemOptions['costFn'] = self.costFn + problem = problemClass(start_state, **problemOptions) + heuristic = getattr(searchAgents, self.heuristicName) if self.heuristicName != None else None + + if heuristic != None: + solution = alg(problem, heuristic) + else: + solution = alg(problem) + + if type(solution) != type([]): + return None, None, 'The result of %s must be a list. (Instead, it is %s)' % (self.alg, type(solution)) + + from game import Directions + dirs = Directions.LEFT.keys() + if [el in dirs for el in solution].count(False) != 0: + return None, None, 'Output of %s must be a list of actions from game.Directions' % self.alg + + expanded = problem._expanded + return solution, expanded, None + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_solution = [str.split(solutionDict['solution']), str.split(solutionDict['rev_solution'])] + gold_expanded = max(int(solutionDict['expanded_nodes']), int(solutionDict['rev_expanded_nodes'])) + + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % error) + return False + + # FIXME: do we want to standardize test output format? + + if solution not in gold_solution: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Solution not correct.') + grades.addMessage('\tstudent solution length: %s' % len(solution)) + grades.addMessage('\tstudent solution:\n%s' % wrap_solution(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length: %s' % len(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution length: %s' % len(gold_solution[1])) + grades.addMessage('\tcorrect solution:\n%s' % wrap_solution(gold_solution[0])) + grades.addMessage('\tcorrect (reversed) solution:\n%s' % wrap_solution(gold_solution[1])) + return False + + if expanded > self.leewayFactor * gold_expanded and expanded > gold_expanded + 1: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Too many node expanded; are you expanding nodes twice?') + grades.addMessage('\tstudent nodes expanded: %s' % expanded) + grades.addMessage('') + grades.addMessage('\tcorrect nodes expanded: %s (leewayFactor %s)' % (gold_expanded, self.leewayFactor)) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length: %s' % len(solution)) + grades.addMessage('\tnodes expanded:\t\t%s' % expanded) + return True + + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# This solution is designed to support both right-to-left\n') + handle.write('# and left-to-right implementations.\n') + handle.write('# Number of nodes expanded must be with a factor of %s of the numbers below.\n' % self.leewayFactor) + + # write forward solution + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('expanded_nodes: "%s"\n' % expanded) + + # write backward solution + search.REVERSE_PUSH = not search.REVERSE_PUSH + solution, expanded, error = self.getSolInfo(search, searchAgents) + if error != None: raise Exception("Error in solution code: %s" % error) + handle.write('rev_solution: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('rev_expanded_nodes: "%s"\n' % expanded) + + # clean up + search.REVERSE_PUSH = not search.REVERSE_PUSH + handle.close() + return True + + +from game import Actions +def getStatesFromPath(start, path): + "Returns the list of states visited along the path" + vis = [start] + curr = start + for a in path: + x,y = curr + dx, dy = Actions.directionToVector(a) + curr = (int(x + dx), int(y + dy)) + vis.append(curr) + return vis + +class CornerProblemTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerProblemTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, search, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problem = searchAgents.CornersProblem(gameState) + path = search.bfs(problem) + + gameState = pacman.GameState() + gameState.initialize(lay, 0) + visited = getStatesFromPath(gameState.getPacmanPosition(), path) + top, right = gameState.getWalls().height-2, gameState.getWalls().width-2 + missedCorners = [p for p in ((1,1), (1,top), (right, 1), (right, top)) if p not in visited] + + return path, missedCorners + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution, missedCorners = self.solution(search, searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('The result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(missedCorners) != 0: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Corners missed: %s' % missedCorners) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Optimal solution not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName) + print(self.layoutText) + + path, _ = self.solution(search, searchAgents) + length = len(path) + print("Problem solved") + + handle.write('solution_length: "%s"\n' % length) + handle.close() + + + + +# template = """class: "HeuristicTest" +# +# heuristic: "foodHeuristic" +# searchProblemClass: "FoodSearchProblem" +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(doneTests + foodTests): +# f = open("food_heuristic_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class HeuristicTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + def checkHeuristic(self, heuristic, problem, state, solutionCost): + h0 = heuristic(state, problem) + + if solutionCost == 0: + if h0 == 0: + return True, '' + else: + return False, 'Heuristic failed H(goal) == 0 test' + + if h0 < 0: + return False, 'Heuristic failed H >= 0 test' + if not h0 > 0: + return False, 'Heuristic failed non-triviality test' + if not h0 <= solutionCost: + return False, 'Heuristic failed admissibility test' + + for succ, action, stepCost in problem.getSuccessors(state): + h1 = heuristic(succ, problem) + if h1 < 0: return False, 'Heuristic failed H >= 0 test' + if h0 - h1 > stepCost: return False, 'Heuristic failed consistency test' + + return True, '' + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + solutionCost = int(solutionDict['solution_cost']) + problem, state, heuristic = self.setupProblem(searchAgents) + + passed, message = self.checkHeuristic(heuristic, problem, state, solutionCost) + + if not passed: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('%s' % message) + return False + else: + grades.addMessage('PASS: %s' % self.path) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName, self.heuristicName) + print(self.layoutText) + problem, _, heuristic = self.setupProblem(searchAgents) + path = search.astar(problem, heuristic) + cost = problem.getCostOfActions(path) + print("Problem solved") + + handle.write('solution_cost: "%s"\n' % cost) + handle.close() + return True + + + + + + +class HeuristicGrade(testClasses.TestCase): + + def __init__(self, question, testDict): + super(HeuristicGrade, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + self.searchProblemClassName = testDict['searchProblemClass'] + self.heuristicName = testDict['heuristic'] + self.basePoints = int(testDict['basePoints']) + self.thresholds = [int(t) for t in testDict['gradingThresholds'].split()] + + def setupProblem(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + problemClass = getattr(searchAgents, self.searchProblemClassName) + problem = problemClass(gameState) + state = problem.getStartState() + heuristic = getattr(searchAgents, self.heuristicName) + + return problem, state, heuristic + + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + problem, _, heuristic = self.setupProblem(searchAgents) + + path = search.astar(problem, heuristic) + + expanded = problem._expanded + + if not checkSolution(problem, path): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tReturned path is not a solution.') + grades.addMessage('\tpath returned by astar: %s' % expanded) + return False + + grades.addPoints(self.basePoints) + points = 0 + for threshold in self.thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(self.thresholds): + grades.addMessage('PASS: %s' % self.path) + else: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\texpanded nodes: %s' % expanded) + grades.addMessage('\tthresholds: %s' % self.thresholds) + + return True + + + def writeSolution(self, moduleDict, filePath): + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + handle.write('# File intentionally blank.\n') + handle.close() + return True + + + + + +# template = """class: "ClosestDotTest" +# +# layoutName: "Test %s" +# layout: \"\"\" +# %s +# \"\"\" +# """ +# +# for i, (_, _, l) in enumerate(foodTests): +# f = open("closest_dot_%s.test" % (i+1), "w") +# f.write(template % (i+1, "\n".join(l))) +# f.close() + +class ClosestDotTest(testClasses.TestCase): + + def __init__(self, question, testDict): + super(ClosestDotTest, self).__init__(question, testDict) + self.layoutText = testDict['layout'] + self.layoutName = testDict['layoutName'] + + def solution(self, searchAgents): + lay = layout.Layout([l.strip() for l in self.layoutText.split('\n')]) + gameState = pacman.GameState() + gameState.initialize(lay, 0) + path = searchAgents.ClosestDotSearchAgent().findPathToClosestDot(gameState) + return path + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + gold_length = int(solutionDict['solution_length']) + solution = self.solution(searchAgents) + + if type(solution) != type([]): + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('\tThe result must be a list. (Instead, it is %s)' % type(solution)) + return False + + if len(solution) != gold_length: + grades.addMessage('FAIL: %s' % self.path) + grades.addMessage('Closest dot not found.') + grades.addMessage('\tstudent solution length:\n%s' % len(solution)) + grades.addMessage('') + grades.addMessage('\tcorrect solution length:\n%s' % gold_length) + return False + + grades.addMessage('PASS: %s' % self.path) + grades.addMessage('\tpacman layout:\t\t%s' % self.layoutName) + grades.addMessage('\tsolution length:\t\t%s' % len(solution)) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # open file and write comments + handle = open(filePath, 'w') + handle.write('# This is the solution file for %s.\n' % self.path) + + print("Solving problem", self.layoutName) + print(self.layoutText) + + length = len(self.solution(searchAgents)) + print("Problem solved") + + handle.write('solution_length: "%s"\n' % length) + handle.close() + return True + + + + +class CornerHeuristicSanity(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicSanity, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + h0 = searchAgents.cornersHeuristic(start_state, problem) + succs = problem.getSuccessors(start_state) + # cornerConsistencyA + for succ in succs: + h1 = searchAgents.cornersHeuristic(succ[0], problem) + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + heuristic_cost = searchAgents.cornersHeuristic(start_state, problem) + true_cost = float(solutionDict['cost']) + # cornerNontrivial + if heuristic_cost == 0: + grades.addMessage('FAIL: must use non-trivial heuristic') + return False + # cornerAdmissible + if heuristic_cost > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = solutionDict['path'].split() + states = followPath(path, problem) + heuristics = [] + for state in states: + heuristics.append(searchAgents.cornersHeuristic(state, problem)) + for i in range(0, len(heuristics) - 1): + h0 = heuristics[i] + h1 = heuristics[i+1] + # cornerConsistencyB + if h0 - h1 > 1: + grades.addMessage('FAIL: inconsistent heuristic') + return False + # cornerPosH + if h0 < 0 or h1 <0: + grades.addMessage('FAIL: non-positive heuristic') + return False + # cornerGoalH + if heuristics[len(heuristics) - 1] != 0: + grades.addMessage('FAIL: heuristic non-zero at goal') + return False + grades.addMessage('PASS: heuristic value less than true cost at start state') + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# In order for a heuristic to be admissible, the value\n') + handle.write('# of the heuristic must be less at each state than the\n') + handle.write('# true cost of the optimal path from that state to a goal.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.close() + return True + + + +class CornerHeuristicPacman(testClasses.TestCase): + + def __init__(self, question, testDict): + super(CornerHeuristicPacman, self).__init__(question, testDict) + self.layout_text = testDict['layout'] + + def execute(self, grades, moduleDict, solutionDict): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + total = 0 + true_cost = float(solutionDict['cost']) + thresholds = [int(x) for x in solutionDict['thresholds'].split()] + game_state = pacman.GameState() + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + game_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(game_state) + start_state = problem.getStartState() + if searchAgents.cornersHeuristic(start_state, problem) > true_cost: + grades.addMessage('FAIL: Inadmissible heuristic') + return False + path = search.astar(problem, searchAgents.cornersHeuristic) + print("path:", path) + print("path length:", len(path)) + cost = problem.getCostOfActions(path) + if cost > true_cost: + grades.addMessage('FAIL: Inconsistent heuristic') + return False + expanded = problem._expanded + points = 0 + for threshold in thresholds: + if expanded <= threshold: + points += 1 + grades.addPoints(points) + if points >= len(thresholds): + grades.addMessage('PASS: Heuristic resulted in expansion of %d nodes' % expanded) + else: + grades.addMessage('FAIL: Heuristic resulted in expansion of %d nodes' % expanded) + return True + + def writeSolution(self, moduleDict, filePath): + search = moduleDict['search'] + searchAgents = moduleDict['searchAgents'] + # write comment + handle = open(filePath, 'w') + handle.write('# This solution file specifies the length of the optimal path\n') + handle.write('# as well as the thresholds on number of nodes expanded to be\n') + handle.write('# used in scoring.\n') + + # solve problem and write solution + lay = layout.Layout([l.strip() for l in self.layout_text.split('\n')]) + start_state = pacman.GameState() + start_state.initialize(lay, 0) + problem = searchAgents.CornersProblem(start_state) + solution = search.astar(problem, searchAgents.cornersHeuristic) + handle.write('cost: "%d"\n' % len(solution)) + handle.write('path: """\n%s\n"""\n' % wrap_solution(solution)) + handle.write('thresholds: "2000 1600 1200"\n') + handle.close() + return True + diff --git a/school/cpsc481/project1/src/submission_autograder.py b/school/cpsc481/project1/src/submission_autograder.py new file mode 100644 index 0000000..f1cd264 --- /dev/null +++ b/school/cpsc481/project1/src/submission_autograder.py @@ -0,0 +1,30 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +from __future__ import print_function +from codecs import open +import os, ssl +if (not os.environ.get('PYTHONHTTPSVERIFY', '') and getattr(ssl, '_create_unverified_context', None)): + ssl._create_default_https_context = ssl._create_unverified_context + +""" +CS 188 Local Submission Autograder +Written by the CS 188 Staff + +============================================================================== + _____ _ _ + / ____| | | | + | (___ | |_ ___ _ __ | | + \___ \| __/ _ \| '_ \| | + ____) | || (_) | |_) |_| + |_____/ \__\___/| .__/(_) + | | + |_| + +Modifying or tampering with this file is a violation of course policy. +If you're having trouble running the autograder, please contact the staff. +============================================================================== +""" +import bz2, base64 +exec(bz2.decompress(base64.b64decode('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'))) + diff --git a/school/cpsc481/project1/src/testClasses.py b/school/cpsc481/project1/src/testClasses.py new file mode 100644 index 0000000..39ee87c --- /dev/null +++ b/school/cpsc481/project1/src/testClasses.py @@ -0,0 +1,206 @@ +# testClasses.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# import modules from python standard library +import inspect +import re +import sys + + +# Class which models a question in a project. Note that questions have a +# maximum number of points they are worth, and are composed of a series of +# test cases +class Question(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def __init__(self, questionDict, display): + self.maxPoints = int(questionDict['max_points']) + self.testCases = [] + self.display = display + + def getDisplay(self): + return self.display + + def getMaxPoints(self): + return self.maxPoints + + # Note that 'thunk' must be a function which accepts a single argument, + # namely a 'grading' object + def addTestCase(self, testCase, thunk): + self.testCases.append((testCase, thunk)) + + def execute(self, grades): + self.raiseNotDefined() + +# Question in which all test cases must be passed in order to receive credit +class PassAllTestsQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + +class ExtraCreditPassAllTestsQuestion(Question): + def __init__(self, questionDict, display): + Question.__init__(self, questionDict, display) + self.extraPoints = int(questionDict['extra_points']) + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + testsFailed = False + grades.assignZeroCredit() + for _, f in self.testCases: + if not f(grades): + testsFailed = True + if testsFailed: + grades.fail("Tests failed.") + else: + grades.assignFullCredit() + grades.addPoints(self.extraPoints) + +# Question in which predict credit is given for test cases with a ``points'' property. +# All other tests are mandatory and must be passed. +class HackedPartialCreditQuestion(Question): + + def execute(self, grades): + # TODO: is this the right way to use grades? The autograder doesn't seem to use it. + grades.assignZeroCredit() + + points = 0 + passed = True + for testCase, f in self.testCases: + testResult = f(grades) + if "points" in testCase.testDict: + if testResult: points += float(testCase.testDict["points"]) + else: + passed = passed and testResult + + ## FIXME: Below terrible hack to match q3's logic + if int(points) == self.maxPoints and not passed: + grades.assignZeroCredit() + else: + grades.addPoints(int(points)) + + +class Q6PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + results = [] + for _, f in self.testCases: + results.append(f(grades)) + if False in results: + grades.assignZeroCredit() + +class PartialCreditQuestion(Question): + """Fails any test which returns False, otherwise doesn't effect the grades object. + Partial credit tests will add the required points.""" + + def execute(self, grades): + grades.assignZeroCredit() + + for _, f in self.testCases: + if not f(grades): + grades.assignZeroCredit() + grades.fail("Tests failed.") + return False + + + +class NumberPassedQuestion(Question): + """Grade is the number of test cases passed.""" + + def execute(self, grades): + grades.addPoints([f(grades) for _, f in self.testCases].count(True)) + + + + + +# Template modeling a generic test case +class TestCase(object): + + def raiseNotDefined(self): + print('Method not implemented: %s' % inspect.stack()[1][3]) + sys.exit(1) + + def getPath(self): + return self.path + + def __init__(self, question, testDict): + self.question = question + self.testDict = testDict + self.path = testDict['path'] + self.messages = [] + + def __str__(self): + self.raiseNotDefined() + + def execute(self, grades, moduleDict, solutionDict): + self.raiseNotDefined() + + def writeSolution(self, moduleDict, filePath): + self.raiseNotDefined() + return True + + # Tests should call the following messages for grading + # to ensure a uniform format for test output. + # + # TODO: this is hairy, but we need to fix grading.py's interface + # to get a nice hierarchical project - question - test structure, + # then these should be moved into Question proper. + def testPass(self, grades): + grades.addMessage('PASS: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return True + + def testFail(self, grades): + grades.addMessage('FAIL: %s' % (self.path,)) + for line in self.messages: + grades.addMessage(' %s' % (line,)) + return False + + # This should really be question level? + # + def testPartial(self, grades, points, maxPoints): + grades.addPoints(points) + extraCredit = max(0, points - maxPoints) + regularCredit = points - extraCredit + + grades.addMessage('%s: %s (%s of %s points)' % ("PASS" if points >= maxPoints else "FAIL", self.path, regularCredit, maxPoints)) + if extraCredit > 0: + grades.addMessage('EXTRA CREDIT: %s points' % (extraCredit,)) + + for line in self.messages: + grades.addMessage(' %s' % (line,)) + + return True + + def addMessage(self, message): + self.messages.extend(message.split('\n')) + diff --git a/school/cpsc481/project1/src/testParser.py b/school/cpsc481/project1/src/testParser.py new file mode 100644 index 0000000..17f3485 --- /dev/null +++ b/school/cpsc481/project1/src/testParser.py @@ -0,0 +1,85 @@ +# testParser.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import re +import sys + +class TestParser(object): + + def __init__(self, path): + # save the path to the test file + self.path = path + + def removeComments(self, rawlines): + # remove any portion of a line following a '#' symbol + fixed_lines = [] + for l in rawlines: + idx = l.find('#') + if idx == -1: + fixed_lines.append(l) + else: + fixed_lines.append(l[0:idx]) + return '\n'.join(fixed_lines) + + def parse(self): + # read in the test case and remove comments + test = {} + with open(self.path) as handle: + raw_lines = handle.read().split('\n') + + test_text = self.removeComments(raw_lines) + test['__raw_lines__'] = raw_lines + test['path'] = self.path + test['__emit__'] = [] + lines = test_text.split('\n') + i = 0 + # read a property in each loop cycle + while(i < len(lines)): + # skip blank lines + if re.match('\A\s*\Z', lines[i]): + test['__emit__'].append(("raw", raw_lines[i])) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"([^"]*)"\s*\Z', lines[i]) + if m: + test[m.group(1)] = m.group(2) + test['__emit__'].append(("oneline", m.group(1))) + i += 1 + continue + m = re.match('\A([^"]*?):\s*"""\s*\Z', lines[i]) + if m: + msg = [] + i += 1 + while(not re.match('\A\s*"""\s*\Z', lines[i])): + msg.append(raw_lines[i]) + i += 1 + test[m.group(1)] = '\n'.join(msg) + test['__emit__'].append(("multiline", m.group(1))) + i += 1 + continue + print('error parsing test file: %s' % self.path) + sys.exit(1) + return test + + +def emitTestDict(testDict, handle): + for kind, data in testDict['__emit__']: + if kind == "raw": + handle.write(data + "\n") + elif kind == "oneline": + handle.write('%s: "%s"\n' % (data, testDict[data])) + elif kind == "multiline": + handle.write('%s: """\n%s\n"""\n' % (data, testDict[data])) + else: + raise Exception("Bad __emit__") diff --git a/school/cpsc481/project1/src/test_cases/CONFIG b/school/cpsc481/project1/src/test_cases/CONFIG new file mode 100644 index 0000000..dbed66b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/CONFIG @@ -0,0 +1 @@ +order: "q1 q2 q3 q4 q5 q6 q7 q8" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q1/CONFIG b/school/cpsc481/project1/src/test_cases/q1/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.solution b/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.solution new file mode 100644 index 0000000..c52850c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A D C" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.test b/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.test new file mode 100644 index 0000000..05640a0 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.solution b/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..0680f92 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->D 0:D->G" +expanded_states: "A D" +rev_solution: "0:A->B 0:B->D 0:D->G" +rev_expanded_states: "A B D" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.test b/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..155e1fe --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_infinite.solution b/school/cpsc481/project1/src/test_cases/q1/graph_infinite.solution new file mode 100644 index 0000000..82203ee --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_infinite.test b/school/cpsc481/project1/src/test_cases/q1/graph_infinite.test new file mode 100644 index 0000000..692ac05 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.solution b/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.solution new file mode 100644 index 0000000..34b5a82 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q1/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "2:A->B2 0:B2->C 0:C->D 2:D->E2 0:E2->F 0:F->G" +expanded_states: "A B2 C D E2 F" +rev_solution: "0:A->B1 0:B1->C 0:C->D 0:D->E1 0:E1->F 0:F->G" +rev_expanded_states: "A B1 C D E1 F" diff --git a/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.test b/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.test new file mode 100644 index 0000000..953c4eb --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "depthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q1/pacman_1.solution b/school/cpsc481/project1/src/test_cases/q1/pacman_1.solution new file mode 100644 index 0000000..82a670c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/pacman_1.solution @@ -0,0 +1,40 @@ +# This is the solution file for test_cases/q1/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East East East East East East East South South South +East East East East East East East South South South South South South +South West West West West West West West West West West West West West +West West West West South West West West West West West West West West +""" +expanded_nodes: "146" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South South East North East East East South +South South West West West West West West West North North North North +North North North North West West West West West West West North North +North East East East East South East East East North North North West +West North North West West West West West West West West West West +West West West West West West West West West West West West West West +South South South South South South South South South East East East +North North North North North North North East East South South South +South South South East East North North North North North North East +East South South South South East East North North North North East +East East East East South South West West West South South East East +East South South West West West West West West South South West West +West West West South West West West West West South South East East +East East East East East North East East East East East North North +East East East East East East North East East East East East South +South West West West South West West West West West West South South +West West West West West South West West West West West West West West +West +""" +rev_expanded_nodes: "269" diff --git a/school/cpsc481/project1/src/test_cases/q1/pacman_1.test b/school/cpsc481/project1/src/test_cases/q1/pacman_1.test new file mode 100644 index 0000000..6ae5412 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q1/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic depth first search test +class: "PacmanSearchTest" +algorithm: "depthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q2/CONFIG b/school/cpsc481/project1/src/test_cases/q2/CONFIG new file mode 100644 index 0000000..ad7e38a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/CONFIG @@ -0,0 +1,2 @@ +max_points: "3" +class: "PassAllTestsQuestion" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.solution b/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.solution new file mode 100644 index 0000000..6c669c2 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A D C B" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.test b/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.test new file mode 100644 index 0000000..2b35d8b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.solution b/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..05eecc8 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A D" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.test b/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..47b78a6 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_infinite.solution b/school/cpsc481/project1/src/test_cases/q2/graph_infinite.solution new file mode 100644 index 0000000..17b621c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_infinite.test b/school/cpsc481/project1/src/test_cases/q2/graph_infinite.test new file mode 100644 index 0000000..2cae9ad --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.solution b/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.solution new file mode 100644 index 0000000..0cea422 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q2/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B2 C B1 D E2 F E1" diff --git a/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.test b/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.test new file mode 100644 index 0000000..7c636ea --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "breadthFirstSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q2/pacman_1.solution b/school/cpsc481/project1/src/test_cases/q2/pacman_1.solution new file mode 100644 index 0000000..8f6d2bd --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/pacman_1.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q2/pacman_1.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.0 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/school/cpsc481/project1/src/test_cases/q2/pacman_1.test b/school/cpsc481/project1/src/test_cases/q2/pacman_1.test new file mode 100644 index 0000000..c913f0c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q2/pacman_1.test @@ -0,0 +1,27 @@ +# This is a basic breadth first search test +class: "PacmanSearchTest" +algorithm: "breadthFirstSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q3/CONFIG b/school/cpsc481/project1/src/test_cases/q3/CONFIG new file mode 100644 index 0000000..e5332c3 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.solution b/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.solution new file mode 100644 index 0000000..d150cb7 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.test b/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.test new file mode 100644 index 0000000..a74bd9e --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.solution b/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.solution new file mode 100644 index 0000000..5dfffca --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_bfs_vs_dfs.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->G" +expanded_states: "A B" +rev_solution: "1:A->G" +rev_expanded_states: "A B" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.test b/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.test new file mode 100644 index 0000000..87aa465 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_bfs_vs_dfs.test @@ -0,0 +1,30 @@ +# Graph where BFS finds the optimal solution but DFS does not +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ +/-- B +| ^ +| | +| *A -->[G] +| | ^ +| V | +\-->D ----/ + +A is the start state, G is the goal. Arrows +mark possible transitions +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->G G 2.0 +A 2:A->D D 4.0 +B 0:B->D D 8.0 +D 0:D->G G 16.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_infinite.solution b/school/cpsc481/project1/src/test_cases/q3/graph_infinite.solution new file mode 100644 index 0000000..c6cd195 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_infinite.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_infinite.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0:A->B 1:B->C 1:C->G" +expanded_states: "A B C" +rev_solution: "0:A->B 1:B->C 1:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_infinite.test b/school/cpsc481/project1/src/test_cases/q3/graph_infinite.test new file mode 100644 index 0000000..80d807f --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_infinite.test @@ -0,0 +1,30 @@ +# Graph where natural action choice leads to an infinite loop +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B <--> C + ^ /| + | / | + V / V +*A<-/ [G] + +A is the start state, G is the goal. Arrows mark +possible state transitions. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +B 0:B->A A 2.0 +B 1:B->C C 4.0 +C 0:C->A A 8.0 +C 1:C->G G 16.0 +C 2:C->B B 32.0 +""" + diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.solution b/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.solution new file mode 100644 index 0000000..628568f --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.test b/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.test new file mode 100644 index 0000000..8c39dc7 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.solution new file mode 100644 index 0000000..b8c1509 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_0_graph.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.test b/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.test new file mode 100644 index 0000000..e8f3d4c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_0_graph.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + |1 + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.solution new file mode 100644 index 0000000..dc8fc4c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_1_problemC.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "269" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "269" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.test b/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.test new file mode 100644 index 0000000..1ce714d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_1_problemC.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +#costFn: "lambda pos: 1" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.solution new file mode 100644 index 0000000..d84245f --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q3/ucs_2_problemE.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +expanded_nodes: "260" +rev_solution: """ +South South West West West West South South East East East East South +South West West West West South South East East East East South South +West West West West South South East East East East South South South +West West West West West West West North West West West West West West +West West West West West West West West West West West South West West +West West West West West West West +""" +rev_expanded_nodes: "260" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.test b/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.test new file mode 100644 index 0000000..3c609f2 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_2_problemE.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: .5 ** pos[0]" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.solution new file mode 100644 index 0000000..e04325d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.solution @@ -0,0 +1,34 @@ +# This is the solution file for test_cases/q3/ucs_3_problemW.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +expanded_nodes: "173" +rev_solution: """ +West West West West West West West West West West West West West West +West West West West West West West West West West West West West West +West West West West West South South South South South South South +South South East East East North North North North North North North +East East South South South South South South East East North North +North North North North East East South South South South East East +North North East East South South East East East South South West West +West West West West South South West West West West West South West +West West West West South South East East East East East East East +North East East East East East North North East East East East East +East South South West West West West South South West West West West +West South West West West West West West West West West +""" +rev_expanded_nodes: "173" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.test b/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.test new file mode 100644 index 0000000..fbc2fad --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_3_problemW.test @@ -0,0 +1,28 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +costFn: "lambda pos: 2 ** pos[0]" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.solution new file mode 100644 index 0000000..b8c5303 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.solution @@ -0,0 +1,12 @@ +# This is the solution file for test_cases/q3/ucs_4_testSearch.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 2.0 of the numbers below. +solution: """ +West East East South South West West +""" +expanded_nodes: "14" +rev_solution: """ +West East East South South West West +""" +rev_expanded_nodes: "13" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.test b/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.test new file mode 100644 index 0000000..a16c6d8 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_4_testSearch.test @@ -0,0 +1,16 @@ +class: "PacmanSearchTest" +algorithm: "uniformCostSearch" +points: "0.5" + +# The following specifies the layout to be used +layoutName: "testSearch" +layout: """ +%%%%% +%.P % +%%% % +%. % +%%%%% +""" +searchProblemClass: "FoodSearchProblem" +leewayFactor: "2" + diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.solution b/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.solution new file mode 100644 index 0000000..7d6c982 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q3/ucs_5_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.test b/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.test new file mode 100644 index 0000000..72b35bc --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q3/ucs_5_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "uniformCostSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/school/cpsc481/project1/src/test_cases/q4/CONFIG b/school/cpsc481/project1/src/test_cases/q4/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_0.solution b/school/cpsc481/project1/src/test_cases/q4/astar_0.solution new file mode 100644 index 0000000..459cadd --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_0.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_0.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "Right Down Down" +expanded_states: "A B D C G" +rev_solution: "Right Down Down" +rev_expanded_states: "A B D C G" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_0.test b/school/cpsc481/project1/src/test_cases/q4/astar_0.test new file mode 100644 index 0000000..9b3b539 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_0.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + C + ^ + | 2 + 2 V 4 +*A <----> B -----> [H] + | + 1.5 V 2.5 + G <----- D -----> E + | + 2 | + V + [F] + +A is the start state, F and H is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: H F +A Right B 2.0 +B Right H 4.0 +B Down D 1.0 +B Up C 2.0 +B Left A 2.0 +C Down B 2.0 +D Right E 2.5 +D Down F 2.0 +D Left G 1.5 +""" + diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.solution b/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.solution new file mode 100644 index 0000000..7767c27 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_1_graph_heuristic.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "0 0 2" +expanded_states: "S A D C" +rev_solution: "0 0 2" +rev_expanded_states: "S A D C" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.test b/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.test new file mode 100644 index 0000000..b5afd79 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_1_graph_heuristic.test @@ -0,0 +1,54 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 2 3 2 + S --- A --- C ---> G + | \ / ^ +3 | \ 5 / 1 / + | \ / / + B --- D -------/ + 4 5 + +S is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +The heuristic value of each state is: + S 6.0 + A 2.5 + B 5.25 + C 1.125 + D 1.0625 + G 0 +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: S +goal_states: G +S 0 A 2.0 +S 1 B 3.0 +S 2 D 5.0 +A 0 C 3.0 +A 1 S 2.0 +B 0 D 4.0 +B 1 S 3.0 +C 0 A 3.0 +C 1 D 1.0 +C 2 G 2.0 +D 0 B 4.0 +D 1 C 1.0 +D 2 G 5.0 +D 3 S 5.0 +""" +heuristic: """ +S 6.0 +A 2.5 +B 5.25 +C 1.125 +D 1.0625 +G 0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.solution b/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.solution new file mode 100644 index 0000000..65bf5f5 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.solution @@ -0,0 +1,22 @@ +# This is the solution file for test_cases/q4/astar_2_manhattan.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +# Number of nodes expanded must be with a factor of 1.1 of the numbers below. +solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +expanded_nodes: "221" +rev_solution: """ +West West West West West West West West West South South East East +South South South West West West North West West West West South South +South East East East East East East East South South South South South +South South West West West West West West West West West West West +West West West West West West South West West West West West West West +West West +""" +rev_expanded_nodes: "221" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.test b/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.test new file mode 100644 index 0000000..e936195 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_2_manhattan.test @@ -0,0 +1,27 @@ +class: "PacmanSearchTest" +algorithm: "aStarSearch" + +# The following specifies the layout to be used +layoutName: "mediumMaze" +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +% P% +% %%%%%%%%%%%%%%%%%%%%%%% %%%%%%%% % +% %% % % %%%%%%% %% % +% %% % % % % %%%% %%%%%%%%% %% %%%%% +% %% % % % % %% %% % +% %% % % % % % %%%% %%% %%%%%% % +% % % % % % %% %%%%%%%% % +% %% % % %%%%%%%% %% %% %%%%% +% %% % %% %%%%%%%%% %% % +% %%%%%% %%%%%%% %% %%%%%% % +%%%%%% % %%%% %% % % +% %%%%%% %%%%% % %% %% %%%%% +% %%%%%% % %%%%% %% % +% %%%%%% %%%%%%%%%%% %% %% % +%%%%%%%%%% %%%%%% % +%. %%%%%%%%%%%%%%%% % +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" +leewayFactor: "1.1" +heuristic: "manhattanHeuristic" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.solution b/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.solution new file mode 100644 index 0000000..edb3502 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/astar_3_goalAtDequeue.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->B 0:B->C 0:C->G" +expanded_states: "A B C" +rev_solution: "1:A->B 0:B->C 0:C->G" +rev_expanded_states: "A B C" diff --git a/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.test b/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.test new file mode 100644 index 0000000..c4d1903 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/astar_3_goalAtDequeue.test @@ -0,0 +1,29 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + 1 1 1 +*A ---> B ---> C ---> [G] + | ^ + | 10 | + \---------------------/ + +A is the start state, G is the goal. Arrows mark possible state +transitions. The number next to the arrow is the cost of that transition. + +If you fail this test case, you may be incorrectly testing if a node is a goal +before adding it into the queue, instead of testing when you remove the node +from the queue. See the algorithm pseudocode in lecture. +""" + +graph: """ +start_state: A +goal_states: G +A 0:A->G G 10.0 +A 1:A->B B 1.0 +B 0:B->C C 1.0 +C 0:C->G G 1.0 +""" +# We only care about the solution, not the expansion order. +exactExpansionOrder: "False" + diff --git a/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.solution b/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.solution new file mode 100644 index 0000000..fc51794 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_backtrack.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->G" +expanded_states: "A B C D" +rev_solution: "1:A->C 0:C->G" +rev_expanded_states: "A B C D" diff --git a/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.test b/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.test new file mode 100644 index 0000000..84e0126 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/graph_backtrack.test @@ -0,0 +1,32 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B + ^ + | +*A --> C --> G + | + V + D + +A is the start state, G is the goal. Arrows mark +possible state transitions. This tests whether +you extract the sequence of actions correctly even +if your search backtracks. If you fail this, your +nodes are not correctly tracking the sequences of +actions required to reach them. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B B 1.0 +A 1:A->C C 2.0 +A 2:A->D D 4.0 +C 0:C->G G 8.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.solution b/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.solution new file mode 100644 index 0000000..0caa767 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.solution @@ -0,0 +1,7 @@ +# This is the solution file for test_cases/q4/graph_manypaths.test. +# This solution is designed to support both right-to-left +# and left-to-right implementations. +solution: "1:A->C 0:C->D 1:D->F 0:F->G" +expanded_states: "A B1 C B2 D E1 F E2" +rev_solution: "1:A->C 0:C->D 1:D->F 0:F->G" +rev_expanded_states: "A B1 C B2 D E1 F E2" diff --git a/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.test b/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.test new file mode 100644 index 0000000..82fdf87 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q4/graph_manypaths.test @@ -0,0 +1,39 @@ +class: "GraphSearchTest" +algorithm: "aStarSearch" + +diagram: """ + B1 E1 + ^ \ ^ \ + / V / V +*A --> C --> D --> F --> [G] + \ ^ \ ^ + V / V / + B2 E2 + +A is the start state, G is the goal. Arrows mark +possible state transitions. This graph has multiple +paths to the goal, where nodes with the same state +are added to the fringe multiple times before they +are expanded. +""" +# The following section specifies the search problem and the solution. +# The graph is specified by first the set of start states, followed by +# the set of goal states, and lastly by the state transitions which are +# of the form: +# +graph: """ +start_state: A +goal_states: G +A 0:A->B1 B1 1.0 +A 1:A->C C 2.0 +A 2:A->B2 B2 4.0 +B1 0:B1->C C 8.0 +B2 0:B2->C C 16.0 +C 0:C->D D 32.0 +D 0:D->E1 E1 64.0 +D 1:D->F F 128.0 +D 2:D->E2 E2 256.0 +E1 0:E1->F F 512.0 +E2 0:E2->F F 1024.0 +F 0:F->G G 2048.0 +""" diff --git a/school/cpsc481/project1/src/test_cases/q5/CONFIG b/school/cpsc481/project1/src/test_cases/q5/CONFIG new file mode 100644 index 0000000..e7c6582 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q5/CONFIG @@ -0,0 +1,3 @@ +class: "PassAllTestsQuestion" +max_points: "3" +depends: "q2" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.solution b/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.solution new file mode 100644 index 0000000..161bf15 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q5/corner_tiny_corner.test. +solution_length: "28" diff --git a/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.test b/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.test new file mode 100644 index 0000000..823bd47 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q5/corner_tiny_corner.test @@ -0,0 +1,14 @@ +class: "CornerProblemTest" + +layoutName: "tinyCorner" +layout: """ +%%%%%%%% +%. .% +% P % +% %%%% % +% % % +% % %%%% +%.% .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q6/CONFIG b/school/cpsc481/project1/src/test_cases/q6/CONFIG new file mode 100644 index 0000000..b76e0eb --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/CONFIG @@ -0,0 +1,3 @@ +class: "Q6PartialCreditQuestion" +max_points: "3" +depends: "q4" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.solution b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.solution new file mode 100644 index 0000000..4385d9b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +North South South East East East North North +""" diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.test b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.test new file mode 100644 index 0000000..93379ac --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_1.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +%P % +%. .% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.solution b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.solution new file mode 100644 index 0000000..1aebe8a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.solution @@ -0,0 +1,7 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "8" +path: """ +West North North East East East South South +""" diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.test b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.test new file mode 100644 index 0000000..18184a8 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_2.test @@ -0,0 +1,12 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%% +%. .% +% %% % +%.P%.% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.solution b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.solution new file mode 100644 index 0000000..c02dd57 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.solution @@ -0,0 +1,9 @@ +# In order for a heuristic to be admissible, the value +# of the heuristic must be less at each state than the +# true cost of the optimal path from that state to a goal. +cost: "28" +path: """ +South South South West West West West East East East East East North +North North North North West West West South South South West West +North North North +""" diff --git a/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.test b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.test new file mode 100644 index 0000000..8f30442 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/corner_sanity_3.test @@ -0,0 +1,15 @@ +class: "CornerHeuristicSanity" +points: "1" + +# The following specifies the layout to be used +layout: """ +%%%%%%%% +%.% .% +% % % % +% % %P % +% % % +%%%%% % +%. .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q6/medium_corners.solution b/school/cpsc481/project1/src/test_cases/q6/medium_corners.solution new file mode 100644 index 0000000..913dc45 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/medium_corners.solution @@ -0,0 +1,16 @@ +# This solution file specifies the length of the optimal path +# as well as the thresholds on number of nodes expanded to be +# used in scoring. +cost: "106" +path: """ +North East East East East North North West West West West North North +North North North North North North West West West West South South +East East East East South South South South South South West West +South South South West West East East North North North East East East +East East East East East South South East East East East East North +North East East North North East East North North East East East East +South South South South East East North North East East South South +South South South North North North North North North North West West +North North East East North North +""" +thresholds: "2000 1600 1200" diff --git a/school/cpsc481/project1/src/test_cases/q6/medium_corners.test b/school/cpsc481/project1/src/test_cases/q6/medium_corners.test new file mode 100644 index 0000000..dfa0a68 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q6/medium_corners.test @@ -0,0 +1,19 @@ +class: "CornerHeuristicPacman" + +# The following specifies the layout to be used +layout: """ +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +%. % % % %.% +% % % %%%%%% %%%%%%% % % +% % % % % % +%%%%% %%%%% %%% %% %%%%% % %%% +% % % % % % % % % +% %%% % % % %%%%%%%% %%% %%% % +% % %% % % % % +%%% % %%%%%%% %%%% %%% % % % % +% % %% % % % +% % %%%%% % %%%% % %%% %%% % % +% % % % % % %%% % +%. %P%%%%% % %%% % .% +%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% +""" diff --git a/school/cpsc481/project1/src/test_cases/q7/CONFIG b/school/cpsc481/project1/src/test_cases/q7/CONFIG new file mode 100644 index 0000000..ee85183 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/CONFIG @@ -0,0 +1,3 @@ +class: "PartialCreditQuestion" +max_points: "4" +depends: "q4" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.solution new file mode 100644 index 0000000..7a287f8 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_1.test. +solution_cost: "0" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.test new file mode 100644 index 0000000..7545a7a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_1.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 1" +layout: """ +%%%%%% +% % +% % +%P % +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.solution new file mode 100644 index 0000000..1917f05 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_10.test. +solution_cost: "7" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.test new file mode 100644 index 0000000..212c7bd --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_10.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 10" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.solution new file mode 100644 index 0000000..11c3289 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_11.test. +solution_cost: "8" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.test new file mode 100644 index 0000000..f5e6ed4 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_11.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 11" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.solution new file mode 100644 index 0000000..0edcc02 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_12.test. +solution_cost: "1" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.test new file mode 100644 index 0000000..cc99a25 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_12.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 12" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.solution new file mode 100644 index 0000000..c25d50b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_13.test. +solution_cost: "5" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.test new file mode 100644 index 0000000..09d6f1e --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_13.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 13" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.solution new file mode 100644 index 0000000..e6cc475 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_14.test. +solution_cost: "31" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.test new file mode 100644 index 0000000..58982e3 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_14.test @@ -0,0 +1,19 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 14" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.solution new file mode 100644 index 0000000..4eca0f1 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_15.test. +solution_cost: "21" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.test new file mode 100644 index 0000000..df605c1 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_15.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 15" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.solution new file mode 100644 index 0000000..8d89992 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_16.test. +solution_cost: "7" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.test new file mode 100644 index 0000000..762b433 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_16.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 16" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.solution new file mode 100644 index 0000000..63a9a1b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_17.test. +solution_cost: "16" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.test new file mode 100644 index 0000000..a923f67 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_17.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 17" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.solution new file mode 100644 index 0000000..ca5aba1 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_2.test. +solution_cost: "0" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.test new file mode 100644 index 0000000..956e75d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_2.test @@ -0,0 +1,32 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 2" +layout: """ +%%% +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +% % +%P% +% % +% % +% % +% % +% % +%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.solution new file mode 100644 index 0000000..d1694b5 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_3.test. +solution_cost: "0" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.test new file mode 100644 index 0000000..250a8b1 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_3.test @@ -0,0 +1,15 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 3" +layout: """ +%%%% +% % +% % +%P % +% % +% % +%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.solution new file mode 100644 index 0000000..6e1e82a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_4.test. +solution_cost: "0" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.test new file mode 100644 index 0000000..ed86a0c --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_4.test @@ -0,0 +1,14 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 4" +layout: """ +%%%%%%%% +% % % +% % %% % +% %P%% % +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.solution new file mode 100644 index 0000000..779e9e6 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_5.test. +solution_cost: "11" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.test new file mode 100644 index 0000000..1f44c48 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_5.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 5" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.solution new file mode 100644 index 0000000..906b510 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_6.test. +solution_cost: "5" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.test new file mode 100644 index 0000000..01d7f32 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_6.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 6" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.solution new file mode 100644 index 0000000..5994a7b --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_7.test. +solution_cost: "7" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.test new file mode 100644 index 0000000..b1db372 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_7.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 7" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.solution new file mode 100644 index 0000000..0e4fb08 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_8.test. +solution_cost: "5" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.test new file mode 100644 index 0000000..b9430af --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_8.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 8" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.solution new file mode 100644 index 0000000..1470d9a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_9.test. +solution_cost: "6" diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.test new file mode 100644 index 0000000..799b41d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_9.test @@ -0,0 +1,13 @@ +class: "HeuristicTest" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "Test 9" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.solution b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.solution new file mode 100644 index 0000000..cd6fd7d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q7/food_heuristic_grade_tricky.test. +# File intentionally blank. diff --git a/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.test b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.test new file mode 100644 index 0000000..081fb0d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q7/food_heuristic_grade_tricky.test @@ -0,0 +1,19 @@ +class: "HeuristicGrade" + +heuristic: "foodHeuristic" +searchProblemClass: "FoodSearchProblem" +layoutName: "trickySearch" +layout: """ +%%%%%%%%%%%%%%%%%%%% +%. ..% % +%.%%.%%.%%.%%.%% % % +% P % % +%%%%%%%%%%%%%%%%%% % +%..... % +%%%%%%%%%%%%%%%%%%%% +""" +# One point always, an extra point for each +# threshold passed. +basePoints: "1" +gradingThresholds: "15000 12000 9000 7000" + diff --git a/school/cpsc481/project1/src/test_cases/q8/CONFIG b/school/cpsc481/project1/src/test_cases/q8/CONFIG new file mode 100644 index 0000000..b24223d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/CONFIG @@ -0,0 +1,2 @@ +class: "PassAllTestsQuestion" +max_points: "3" \ No newline at end of file diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.solution new file mode 100644 index 0000000..300fc25 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_1.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.test new file mode 100644 index 0000000..672989f --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_1.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 1" +layout: """ +%%%%%% +%....% +%....% +%P...% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.solution new file mode 100644 index 0000000..174b5dd --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_10.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.test new file mode 100644 index 0000000..b1e0f33 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_10.test @@ -0,0 +1,17 @@ +class: "ClosestDotTest" + +layoutName: "Test 10" +layout: """ +%%%%%%%%%% +% % +% ...%...% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +% .%.%.%.% +%P.%...%.% +% % +%%%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.solution new file mode 100644 index 0000000..80bbe38 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_11.test. +solution_length: "2" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.test new file mode 100644 index 0000000..0310a1e --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_11.test @@ -0,0 +1,30 @@ +class: "ClosestDotTest" + +layoutName: "Test 11" +layout: """ +%%% +% % +% % +% % +% % +% % +%.% +%.% +% % +% % +% % +% % +% % +% % +% % +%.% +% % +%P% +% % +% % +% % +% % +%.% +%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.solution new file mode 100644 index 0000000..6f38bcb --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_12.test. +solution_length: "3" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.test new file mode 100644 index 0000000..a17b628 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_12.test @@ -0,0 +1,13 @@ +class: "ClosestDotTest" + +layoutName: "Test 12" +layout: """ +%%%% +% .% +% % +%P % +% % +% .% +%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.solution new file mode 100644 index 0000000..7afa908 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_13.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.test new file mode 100644 index 0000000..87c423d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_13.test @@ -0,0 +1,12 @@ +class: "ClosestDotTest" + +layoutName: "Test 13" +layout: """ +%%%%%%%% +%.%....% +%.% %%.% +%.%P%%.% +%... .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.solution new file mode 100644 index 0000000..16d75de --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_2.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.test new file mode 100644 index 0000000..4b59602 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_2.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 2" +layout: """ +%%%%%% +% .% +%.P..% +% % +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.solution new file mode 100644 index 0000000..cbd5974 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_3.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.test new file mode 100644 index 0000000..aa2a3af --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_3.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 3" +layout: """ +%%%%%%% +% .% +%. P..% +% % +%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.solution new file mode 100644 index 0000000..ca520b5 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_4.test. +solution_length: "3" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.test new file mode 100644 index 0000000..8499f6d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_4.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 4" +layout: """ +%%%%%% +% .% +% .% +%P .% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.solution new file mode 100644 index 0000000..5c526a2 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_5.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.test new file mode 100644 index 0000000..dfaee3d --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_5.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 5" +layout: """ +%%%%%% +% %. % +% %%.% +%P. .% +%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.solution new file mode 100644 index 0000000..b06468a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_6.test. +solution_length: "2" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.test new file mode 100644 index 0000000..bc50c57 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_6.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 6" +layout: """ +%%%%%%%% +% % +%. P .% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.solution new file mode 100644 index 0000000..3231b28 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_7.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.test new file mode 100644 index 0000000..746e89a --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_7.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 7" +layout: """ +%%%%%%%% +% % +% P % +%. . .% +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.solution new file mode 100644 index 0000000..646e621 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_8.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.test new file mode 100644 index 0000000..c266ae1 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_8.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 8" +layout: """ +%%%%%%%% +% % +% P.% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.solution b/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.solution new file mode 100644 index 0000000..6c94aa5 --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.solution @@ -0,0 +1,2 @@ +# This is the solution file for test_cases/q8/closest_dot_9.test. +solution_length: "1" diff --git a/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.test b/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.test new file mode 100644 index 0000000..da078de --- /dev/null +++ b/school/cpsc481/project1/src/test_cases/q8/closest_dot_9.test @@ -0,0 +1,11 @@ +class: "ClosestDotTest" + +layoutName: "Test 9" +layout: """ +%%%%%%%% +% % +%P. .% +% % +%%%%%%%% +""" + diff --git a/school/cpsc481/project1/src/textDisplay.py b/school/cpsc481/project1/src/textDisplay.py new file mode 100644 index 0000000..8e25a46 --- /dev/null +++ b/school/cpsc481/project1/src/textDisplay.py @@ -0,0 +1,81 @@ +# textDisplay.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import time +try: + import pacman +except: + pass + +DRAW_EVERY = 1 +SLEEP_TIME = 0 # This can be overwritten by __init__ +DISPLAY_MOVES = False +QUIET = False # Supresses output + +class NullGraphics: + def initialize(self, state, isBlue = False): + pass + + def update(self, state): + pass + + def checkNullDisplay(self): + return True + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def updateDistributions(self, dist): + pass + + def finish(self): + pass + +class PacmanGraphics: + def __init__(self, speed=None): + if speed != None: + global SLEEP_TIME + SLEEP_TIME = speed + + def initialize(self, state, isBlue = False): + self.draw(state) + self.pause() + self.turn = 0 + self.agentCounter = 0 + + def update(self, state): + numAgents = len(state.agentStates) + self.agentCounter = (self.agentCounter + 1) % numAgents + if self.agentCounter == 0: + self.turn += 1 + if DISPLAY_MOVES: + ghosts = [pacman.nearestPoint(state.getGhostPosition(i)) for i in range(1, numAgents)] + print("%4d) P: %-8s" % (self.turn, str(pacman.nearestPoint(state.getPacmanPosition()))),'| Score: %-5d' % state.score,'| Ghosts:', ghosts) + if self.turn % DRAW_EVERY == 0: + self.draw(state) + self.pause() + if state._win or state._lose: + self.draw(state) + + def pause(self): + time.sleep(SLEEP_TIME) + + def draw(self, state): + print(state) + + def finish(self): + pass diff --git a/school/cpsc481/project1/src/util.py b/school/cpsc481/project1/src/util.py new file mode 100644 index 0000000..d81667f --- /dev/null +++ b/school/cpsc481/project1/src/util.py @@ -0,0 +1,672 @@ +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# util.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import sys +import inspect +import heapq, random + + +class FixedRandom: + def __init__(self): + fixedState = (3, (2147483648, 507801126, 683453281, 310439348, 2597246090, \ + 2209084787, 2267831527, 979920060, 3098657677, 37650879, 807947081, 3974896263, \ + 881243242, 3100634921, 1334775171, 3965168385, 746264660, 4074750168, 500078808, \ + 776561771, 702988163, 1636311725, 2559226045, 157578202, 2498342920, 2794591496, \ + 4130598723, 496985844, 2944563015, 3731321600, 3514814613, 3362575829, 3038768745, \ + 2206497038, 1108748846, 1317460727, 3134077628, 988312410, 1674063516, 746456451, \ + 3958482413, 1857117812, 708750586, 1583423339, 3466495450, 1536929345, 1137240525, \ + 3875025632, 2466137587, 1235845595, 4214575620, 3792516855, 657994358, 1241843248, \ + 1695651859, 3678946666, 1929922113, 2351044952, 2317810202, 2039319015, 460787996, \ + 3654096216, 4068721415, 1814163703, 2904112444, 1386111013, 574629867, 2654529343, \ + 3833135042, 2725328455, 552431551, 4006991378, 1331562057, 3710134542, 303171486, \ + 1203231078, 2670768975, 54570816, 2679609001, 578983064, 1271454725, 3230871056, \ + 2496832891, 2944938195, 1608828728, 367886575, 2544708204, 103775539, 1912402393, \ + 1098482180, 2738577070, 3091646463, 1505274463, 2079416566, 659100352, 839995305, \ + 1696257633, 274389836, 3973303017, 671127655, 1061109122, 517486945, 1379749962, \ + 3421383928, 3116950429, 2165882425, 2346928266, 2892678711, 2936066049, 1316407868, \ + 2873411858, 4279682888, 2744351923, 3290373816, 1014377279, 955200944, 4220990860, \ + 2386098930, 1772997650, 3757346974, 1621616438, 2877097197, 442116595, 2010480266, \ + 2867861469, 2955352695, 605335967, 2222936009, 2067554933, 4129906358, 1519608541, \ + 1195006590, 1942991038, 2736562236, 279162408, 1415982909, 4099901426, 1732201505, \ + 2934657937, 860563237, 2479235483, 3081651097, 2244720867, 3112631622, 1636991639, \ + 3860393305, 2312061927, 48780114, 1149090394, 2643246550, 1764050647, 3836789087, \ + 3474859076, 4237194338, 1735191073, 2150369208, 92164394, 756974036, 2314453957, \ + 323969533, 4267621035, 283649842, 810004843, 727855536, 1757827251, 3334960421, \ + 3261035106, 38417393, 2660980472, 1256633965, 2184045390, 811213141, 2857482069, \ + 2237770878, 3891003138, 2787806886, 2435192790, 2249324662, 3507764896, 995388363, \ + 856944153, 619213904, 3233967826, 3703465555, 3286531781, 3863193356, 2992340714, \ + 413696855, 3865185632, 1704163171, 3043634452, 2225424707, 2199018022, 3506117517, \ + 3311559776, 3374443561, 1207829628, 668793165, 1822020716, 2082656160, 1160606415, \ + 3034757648, 741703672, 3094328738, 459332691, 2702383376, 1610239915, 4162939394, \ + 557861574, 3805706338, 3832520705, 1248934879, 3250424034, 892335058, 74323433, \ + 3209751608, 3213220797, 3444035873, 3743886725, 1783837251, 610968664, 580745246, \ + 4041979504, 201684874, 2673219253, 1377283008, 3497299167, 2344209394, 2304982920, \ + 3081403782, 2599256854, 3184475235, 3373055826, 695186388, 2423332338, 222864327, \ + 1258227992, 3627871647, 3487724980, 4027953808, 3053320360, 533627073, 3026232514, \ + 2340271949, 867277230, 868513116, 2158535651, 2487822909, 3428235761, 3067196046, \ + 3435119657, 1908441839, 788668797, 3367703138, 3317763187, 908264443, 2252100381, \ + 764223334, 4127108988, 384641349, 3377374722, 1263833251, 1958694944, 3847832657, \ + 1253909612, 1096494446, 555725445, 2277045895, 3340096504, 1383318686, 4234428127, \ + 1072582179, 94169494, 1064509968, 2681151917, 2681864920, 734708852, 1338914021, \ + 1270409500, 1789469116, 4191988204, 1716329784, 2213764829, 3712538840, 919910444, \ + 1318414447, 3383806712, 3054941722, 3378649942, 1205735655, 1268136494, 2214009444, \ + 2532395133, 3232230447, 230294038, 342599089, 772808141, 4096882234, 3146662953, \ + 2784264306, 1860954704, 2675279609, 2984212876, 2466966981, 2627986059, 2985545332, \ + 2578042598, 1458940786, 2944243755, 3959506256, 1509151382, 325761900, 942251521, \ + 4184289782, 2756231555, 3297811774, 1169708099, 3280524138, 3805245319, 3227360276, \ + 3199632491, 2235795585, 2865407118, 36763651, 2441503575, 3314890374, 1755526087, \ + 17915536, 1196948233, 949343045, 3815841867, 489007833, 2654997597, 2834744136, \ + 417688687, 2843220846, 85621843, 747339336, 2043645709, 3520444394, 1825470818, \ + 647778910, 275904777, 1249389189, 3640887431, 4200779599, 323384601, 3446088641, \ + 4049835786, 1718989062, 3563787136, 44099190, 3281263107, 22910812, 1826109246, \ + 745118154, 3392171319, 1571490704, 354891067, 815955642, 1453450421, 940015623, \ + 796817754, 1260148619, 3898237757, 176670141, 1870249326, 3317738680, 448918002, \ + 4059166594, 2003827551, 987091377, 224855998, 3520570137, 789522610, 2604445123, \ + 454472869, 475688926, 2990723466, 523362238, 3897608102, 806637149, 2642229586, \ + 2928614432, 1564415411, 1691381054, 3816907227, 4082581003, 1895544448, 3728217394, \ + 3214813157, 4054301607, 1882632454, 2873728645, 3694943071, 1297991732, 2101682438, \ + 3952579552, 678650400, 1391722293, 478833748, 2976468591, 158586606, 2576499787, \ + 662690848, 3799889765, 3328894692, 2474578497, 2383901391, 1718193504, 3003184595, \ + 3630561213, 1929441113, 3848238627, 1594310094, 3040359840, 3051803867, 2462788790, \ + 954409915, 802581771, 681703307, 545982392, 2738993819, 8025358, 2827719383, \ + 770471093, 3484895980, 3111306320, 3900000891, 2116916652, 397746721, 2087689510, \ + 721433935, 1396088885, 2751612384, 1998988613, 2135074843, 2521131298, 707009172, \ + 2398321482, 688041159, 2264560137, 482388305, 207864885, 3735036991, 3490348331, \ + 1963642811, 3260224305, 3493564223, 1939428454, 1128799656, 1366012432, 2858822447, \ + 1428147157, 2261125391, 1611208390, 1134826333, 2374102525, 3833625209, 2266397263, \ + 3189115077, 770080230, 2674657172, 4280146640, 3604531615, 4235071805, 3436987249, \ + 509704467, 2582695198, 4256268040, 3391197562, 1460642842, 1617931012, 457825497, \ + 1031452907, 1330422862, 4125947620, 2280712485, 431892090, 2387410588, 2061126784, \ + 896457479, 3480499461, 2488196663, 4021103792, 1877063114, 2744470201, 1046140599, \ + 2129952955, 3583049218, 4217723693, 2720341743, 820661843, 1079873609, 3360954200, \ + 3652304997, 3335838575, 2178810636, 1908053374, 4026721976, 1793145418, 476541615, \ + 973420250, 515553040, 919292001, 2601786155, 1685119450, 3030170809, 1590676150, \ + 1665099167, 651151584, 2077190587, 957892642, 646336572, 2743719258, 866169074, \ + 851118829, 4225766285, 963748226, 799549420, 1955032629, 799460000, 2425744063, \ + 2441291571, 1928963772, 528930629, 2591962884, 3495142819, 1896021824, 901320159, \ + 3181820243, 843061941, 3338628510, 3782438992, 9515330, 1705797226, 953535929, \ + 764833876, 3202464965, 2970244591, 519154982, 3390617541, 566616744, 3438031503, \ + 1853838297, 170608755, 1393728434, 676900116, 3184965776, 1843100290, 78995357, \ + 2227939888, 3460264600, 1745705055, 1474086965, 572796246, 4081303004, 882828851, \ + 1295445825, 137639900, 3304579600, 2722437017, 4093422709, 273203373, 2666507854, \ + 3998836510, 493829981, 1623949669, 3482036755, 3390023939, 833233937, 1639668730, \ + 1499455075, 249728260, 1210694006, 3836497489, 1551488720, 3253074267, 3388238003, \ + 2372035079, 3945715164, 2029501215, 3362012634, 2007375355, 4074709820, 631485888, \ + 3135015769, 4273087084, 3648076204, 2739943601, 1374020358, 1760722448, 3773939706, \ + 1313027823, 1895251226, 4224465911, 421382535, 1141067370, 3660034846, 3393185650, \ + 1850995280, 1451917312, 3841455409, 3926840308, 1397397252, 2572864479, 2500171350, \ + 3119920613, 531400869, 1626487579, 1099320497, 407414753, 2438623324, 99073255, \ + 3175491512, 656431560, 1153671785, 236307875, 2824738046, 2320621382, 892174056, \ + 230984053, 719791226, 2718891946, 624), None) + self.random = random.Random() + self.random.setstate(fixedState) + +""" + Data structures useful for implementing SearchAgents +""" + +class Stack: + "A container with a last-in-first-out (LIFO) queuing policy." + def __init__(self): + self.list = [] + + def push(self,item): + "Push 'item' onto the stack" + self.list.append(item) + + def pop(self): + "Pop the most recently pushed item from the stack" + return self.list.pop() + + def isEmpty(self): + "Returns true if the stack is empty" + return len(self.list) == 0 + +class Queue: + "A container with a first-in-first-out (FIFO) queuing policy." + def __init__(self): + self.list = [] + + def push(self,item): + "Enqueue the 'item' into the queue" + self.list.insert(0,item) + + def pop(self): + """ + Dequeue the earliest enqueued item still in the queue. This + operation removes the item from the queue. + """ + return self.list.pop() + + def isEmpty(self): + "Returns true if the queue is empty" + return len(self.list) == 0 + +class PriorityQueue: + """ + Implements a priority queue data structure. Each inserted item + has a priority associated with it and the client is usually interested + in quick retrieval of the lowest-priority item in the queue. This + data structure allows O(1) access to the lowest-priority item. + """ + def __init__(self): + self.heap = [] + self.count = 0 + + def push(self, item, priority): + entry = (priority, self.count, item) + heapq.heappush(self.heap, entry) + self.count += 1 + + def pop(self): + (_, _, item) = heapq.heappop(self.heap) + return item + + def isEmpty(self): + return len(self.heap) == 0 + + def update(self, item, priority): + # If item already in priority queue with higher priority, update its priority and rebuild the heap. + # If item already in priority queue with equal or lower priority, do nothing. + # If item not in priority queue, do the same thing as self.push. + for index, (p, c, i) in enumerate(self.heap): + if i == item: + if p <= priority: + break + del self.heap[index] + self.heap.append((priority, c, item)) + heapq.heapify(self.heap) + break + else: + self.push(item, priority) + +class PriorityQueueWithFunction(PriorityQueue): + """ + Implements a priority queue with the same push/pop signature of the + Queue and the Stack classes. This is designed for drop-in replacement for + those two classes. The caller has to provide a priority function, which + extracts each item's priority. + """ + def __init__(self, priorityFunction): + "priorityFunction (item) -> priority" + self.priorityFunction = priorityFunction # store the priority function + PriorityQueue.__init__(self) # super-class initializer + + def push(self, item): + "Adds an item to the queue with priority from the priority function" + PriorityQueue.push(self, item, self.priorityFunction(item)) + + +def manhattanDistance( xy1, xy2 ): + "Returns the Manhattan distance between points xy1 and xy2" + return abs( xy1[0] - xy2[0] ) + abs( xy1[1] - xy2[1] ) + +""" + Data structures and functions useful for various course projects + + The search project should not need anything below this line. +""" + +class Counter(dict): + """ + A counter keeps track of counts for a set of keys. + + The counter class is an extension of the standard python + dictionary type. It is specialized to have number values + (integers or floats), and includes a handful of additional + functions to ease the task of counting data. In particular, + all keys are defaulted to have value 0. Using a dictionary: + + a = {} + print(a['test']) + + would give an error, while the Counter class analogue: + + >>> a = Counter() + >>> print(a['test']) + 0 + + returns the default 0 value. Note that to reference a key + that you know is contained in the counter, + you can still use the dictionary syntax: + + >>> a = Counter() + >>> a['test'] = 2 + >>> print(a['test']) + 2 + + This is very useful for counting things without initializing their counts, + see for example: + + >>> a['blah'] += 1 + >>> print(a['blah']) + 1 + + The counter also includes additional functionality useful in implementing + the classifiers for this assignment. Two counters can be added, + subtracted or multiplied together. See below for details. They can + also be normalized and their total count and arg max can be extracted. + """ + def __getitem__(self, idx): + self.setdefault(idx, 0) + return dict.__getitem__(self, idx) + + def incrementAll(self, keys, count): + """ + Increments all elements of keys by the same count. + + >>> a = Counter() + >>> a.incrementAll(['one','two', 'three'], 1) + >>> a['one'] + 1 + >>> a['two'] + 1 + """ + for key in keys: + self[key] += count + + def argMax(self): + """ + Returns the key with the highest value. + """ + if len(self.keys()) == 0: return None + all = self.items() + values = [x[1] for x in all] + maxIndex = values.index(max(values)) + return all[maxIndex][0] + + def sortedKeys(self): + """ + Returns a list of keys sorted by their values. Keys + with the highest values will appear first. + + >>> a = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> a['third'] = 1 + >>> a.sortedKeys() + ['second', 'third', 'first'] + """ + sortedItems = self.items() + compare = lambda x, y: sign(y[1] - x[1]) + sortedItems.sort(cmp=compare) + return [x[0] for x in sortedItems] + + def totalCount(self): + """ + Returns the sum of counts for all keys. + """ + return sum(self.values()) + + def normalize(self): + """ + Edits the counter such that the total count of all + keys sums to 1. The ratio of counts for all keys + will remain the same. Note that normalizing an empty + Counter will result in an error. + """ + total = float(self.totalCount()) + if total == 0: return + for key in self.keys(): + self[key] = self[key] / total + + def divideAll(self, divisor): + """ + Divides all counts by divisor + """ + divisor = float(divisor) + for key in self: + self[key] /= divisor + + def copy(self): + """ + Returns a copy of the counter + """ + return Counter(dict.copy(self)) + + def __mul__(self, y ): + """ + Multiplying two counters gives the dot product of their vectors where + each unique label is a vector element. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['second'] = 5 + >>> a['third'] = 1.5 + >>> a['fourth'] = 2.5 + >>> a * b + 14 + """ + sum = 0 + x = self + if len(x) > len(y): + x,y = y,x + for key in x: + if key not in y: + continue + sum += x[key] * y[key] + return sum + + def __radd__(self, y): + """ + Adding another counter to a counter increments the current counter + by the values stored in the second counter. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> a += b + >>> a['first'] + 1 + """ + for key, value in y.items(): + self[key] += value + + def __add__( self, y ): + """ + Adding two counters gives a counter with the union of all keys and + counts of the second added to counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a + b)['first'] + 1 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] + y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = y[key] + return addend + + def __sub__( self, y ): + """ + Subtracting a counter from another gives a counter with the union of all keys and + counts of the second subtracted from counts of the first. + + >>> a = Counter() + >>> b = Counter() + >>> a['first'] = -2 + >>> a['second'] = 4 + >>> b['first'] = 3 + >>> b['third'] = 1 + >>> (a - b)['first'] + -5 + """ + addend = Counter() + for key in self: + if key in y: + addend[key] = self[key] - y[key] + else: + addend[key] = self[key] + for key in y: + if key in self: + continue + addend[key] = -1 * y[key] + return addend + +def raiseNotDefined(): + fileName = inspect.stack()[1][1] + line = inspect.stack()[1][2] + method = inspect.stack()[1][3] + + print("*** Method not implemented: %s at line %s of %s" % (method, line, fileName)) + sys.exit(1) + +def normalize(vectorOrCounter): + """ + normalize a vector or counter by dividing each value by the sum of all values + """ + normalizedCounter = Counter() + if type(vectorOrCounter) == type(normalizedCounter): + counter = vectorOrCounter + total = float(counter.totalCount()) + if total == 0: return counter + for key in counter.keys(): + value = counter[key] + normalizedCounter[key] = value / total + return normalizedCounter + else: + vector = vectorOrCounter + s = float(sum(vector)) + if s == 0: return vector + return [el / s for el in vector] + +def nSample(distribution, values, n): + if sum(distribution) != 1: + distribution = normalize(distribution) + rand = [random.random() for i in range(n)] + rand.sort() + samples = [] + samplePos, distPos, cdf = 0,0, distribution[0] + while samplePos < n: + if rand[samplePos] < cdf: + samplePos += 1 + samples.append(values[distPos]) + else: + distPos += 1 + cdf += distribution[distPos] + return samples + +def sample(distribution, values = None): + if type(distribution) == Counter: + items = sorted(distribution.items()) + distribution = [i[1] for i in items] + values = [i[0] for i in items] + if sum(distribution) != 1: + distribution = normalize(distribution) + choice = random.random() + i, total= 0, distribution[0] + while choice > total: + i += 1 + total += distribution[i] + return values[i] + +def sampleFromCounter(ctr): + items = sorted(ctr.items()) + return sample([v for k,v in items], [k for k,v in items]) + +def getProbability(value, distribution, values): + """ + Gives the probability of a value under a discrete distribution + defined by (distributions, values). + """ + total = 0.0 + for prob, val in zip(distribution, values): + if val == value: + total += prob + return total + +def flipCoin( p ): + r = random.random() + return r < p + +def chooseFromDistribution( distribution ): + "Takes either a counter or a list of (prob, key) pairs and samples" + if type(distribution) == dict or type(distribution) == Counter: + return sample(distribution) + r = random.random() + base = 0.0 + for prob, element in distribution: + base += prob + if r <= base: return element + +def nearestPoint( pos ): + """ + Finds the nearest grid point to a position (discretizes). + """ + ( current_row, current_col ) = pos + + grid_row = int( current_row + 0.5 ) + grid_col = int( current_col + 0.5 ) + return ( grid_row, grid_col ) + +def sign( x ): + """ + Returns 1 or -1 depending on the sign of x + """ + if( x >= 0 ): + return 1 + else: + return -1 + +def arrayInvert(array): + """ + Inverts a matrix stored as a list of lists. + """ + result = [[] for i in array] + for outer in array: + for inner in range(len(outer)): + result[inner].append(outer[inner]) + return result + +def matrixAsList( matrix, value = True ): + """ + Turns a matrix into a list of coordinates matching the specified value + """ + rows, cols = len( matrix ), len( matrix[0] ) + cells = [] + for row in range( rows ): + for col in range( cols ): + if matrix[row][col] == value: + cells.append( ( row, col ) ) + return cells + +def lookup(name, namespace): + """ + Get a method or class from any imported module from its name. + Usage: lookup(functionName, globals()) + """ + dots = name.count('.') + if dots > 0: + moduleName, objName = '.'.join(name.split('.')[:-1]), name.split('.')[-1] + module = __import__(moduleName) + return getattr(module, objName) + else: + modules = [obj for obj in namespace.values() if str(type(obj)) == ""] + options = [getattr(module, name) for module in modules if name in dir(module)] + options += [obj[1] for obj in namespace.items() if obj[0] == name ] + if len(options) == 1: return options[0] + if len(options) > 1: raise Exception('Name conflict for %s') + raise Exception('%s not found as a method or class' % name) + +def pause(): + """ + Pauses the output stream awaiting user feedback. + """ + input("") + + +# code to handle timeouts +# +# FIXME +# NOTE: TimeoutFuncton is NOT reentrant. Later timeouts will silently +# disable earlier timeouts. Could be solved by maintaining a global list +# of active time outs. Currently, questions which have test cases calling +# this have all student code so wrapped. +# +import signal +import time +class TimeoutFunctionException(Exception): + """Exception to raise on a timeout""" + pass + + +class TimeoutFunction: + def __init__(self, function, timeout): + self.timeout = timeout + self.function = function + + def handle_timeout(self, signum, frame): + raise TimeoutFunctionException() + + def __call__(self, *args, **keyArgs): + # If we have SIGALRM signal, use it to cause an exception if and + # when this function runs too long. Otherwise check the time taken + # after the method has returned, and throw an exception then. + if hasattr(signal, 'SIGALRM'): + old = signal.signal(signal.SIGALRM, self.handle_timeout) + signal.alarm(self.timeout) + try: + result = self.function(*args, **keyArgs) + finally: + signal.signal(signal.SIGALRM, old) + signal.alarm(0) + else: + startTime = time.time() + result = self.function(*args, **keyArgs) + timeElapsed = time.time() - startTime + if timeElapsed >= self.timeout: + self.handle_timeout(None, None) + return result + + + +_ORIGINAL_STDOUT = None +_ORIGINAL_STDERR = None +_MUTED = False + +class WritableNull: + def write(self, string): + pass + +def mutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if _MUTED: + return + _MUTED = True + + _ORIGINAL_STDOUT = sys.stdout + #_ORIGINAL_STDERR = sys.stderr + sys.stdout = WritableNull() + #sys.stderr = WritableNull() + +def unmutePrint(): + global _ORIGINAL_STDOUT, _ORIGINAL_STDERR, _MUTED + if not _MUTED: + return + _MUTED = False + + sys.stdout = _ORIGINAL_STDOUT + #sys.stderr = _ORIGINAL_STDERR + diff --git a/school/cpsc481/project2/.gitignore b/school/cpsc481/project2/.gitignore new file mode 100644 index 0000000..b6e4761 --- /dev/null +++ b/school/cpsc481/project2/.gitignore @@ -0,0 +1,129 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +pip-wheel-metadata/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# 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/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +.python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ diff --git a/school/cpsc481/project2/LICENSE b/school/cpsc481/project2/LICENSE new file mode 100644 index 0000000..407d5a9 --- /dev/null +++ b/school/cpsc481/project2/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2021 Jared Dyreson + +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. diff --git a/school/cpsc481/project2/README.md b/school/cpsc481/project2/README.md new file mode 100644 index 0000000..138c017 --- /dev/null +++ b/school/cpsc481/project2/README.md @@ -0,0 +1,2 @@ +# pacman-ai +A Pacman maze solving project for CS 481 diff --git a/school/cpsc481/project2/src/VERSION b/school/cpsc481/project2/src/VERSION new file mode 100644 index 0000000..6af849e --- /dev/null +++ b/school/cpsc481/project2/src/VERSION @@ -0,0 +1 @@ +v1.002 diff --git a/school/cpsc481/project2/src/algorithms/backtracking/main.py b/school/cpsc481/project2/src/algorithms/backtracking/main.py new file mode 100644 index 0000000..0a99037 --- /dev/null +++ b/school/cpsc481/project2/src/algorithms/backtracking/main.py @@ -0,0 +1,88 @@ +""" +Example implementation of backtracking search in Python +""" + +class node: + def __init__(self, label: str): + if(not isinstance(label, str)): + raise ValueError + self.label = label + + def __hash__(self): + return hash(self.label) + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +children = ["A", "B", "C", "D"] +d = ["A"] +s = ["B"] +nsl = ["C"] + +def l_difference(*args): + if not(all([isinstance(i, list) for i in args])): + raise ValueError + + container = [] + for element in args: + subcontainer = [i for i in element if i not in container] + container.extend(subcontainer) + return container + +def l_union(a: list, b: list): + # TODO : DUPLICATE CODE + + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + +def backtracking(starting: str, GD: list) -> list: + if(not isinstance(starting, str) or + not isinstance(GD, list)): + raise ValueError + + state_list, new_state_list = [starting], [starting] + dead_ends = [] + current_state = starting + if(current_state in GD): + return state_list + + children = graph[current_state] + if(not children) + +# open_nodes = ["A"] + +# state_list = open_nodes +# new_state_list = open_nodes +# dead_ends = [] +# current_state = "A" +# goal_states = ["X"] + +while(state_list): + # current_state = state_list.pop() + print(current_state) + if(current_state in goal_states): + print(f'[complete] {state_list}') # should be return + children = graph[current_state] + if(not children): + while(state_list and current_state == state_list[0]): + dead_ends.append(current_state) + state_list.pop() + new_state_list.pop() + current_state = new_state_list[0] + state_list.append(current_state) + else: + new_state_list.extend(children) + current_state = new_state_list[0] + state_list.append(current_state) diff --git a/school/cpsc481/project2/src/algorithms/bfs/main.py b/school/cpsc481/project2/src/algorithms/bfs/main.py new file mode 100644 index 0000000..9d6e7ae --- /dev/null +++ b/school/cpsc481/project2/src/algorithms/bfs/main.py @@ -0,0 +1,61 @@ +""" +Example implementation of BFS in Python +""" + +import unittest + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +def l_union(a: list, b: list): + # TODO : DUPLICATE CODE + + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + + +def bfs(G: dict, S: list, GD: list) -> bool: # -> (TYPE BEING RETURNED) + # type enforcement + if(not isinstance(S, list) or # open nodes + not isinstance(GD, list) or # acceptance states + not isinstance(G, dict)): # graph in question + raise ValueError + + X = None + open_nodes = S + goal_states = GD + closed = [] + + while(open_nodes): + X = open_nodes.pop() + if(X in goal_states): + return True + + children = l_union(graph[X], open_nodes) + open_nodes.extend(children) # append the contents of the lookup while removing duplicates from both containers + closed.append(X) + + return False + +class TestBFS(unittest.TestCase): + def test_bfs_valid_dest(self): + S, GD = ["A"], ["X"] + self.assertTrue(bfs(graph, S, GD)) + def test_bfs_invalid_dest(self): + S, GD = ["A"], ["Z"] + self.assertFalse(bfs(graph, S, GD)) + +if __name__ == '__main__': + unittest.main() diff --git a/school/cpsc481/project2/src/algorithms/dfs/main.py b/school/cpsc481/project2/src/algorithms/dfs/main.py new file mode 100644 index 0000000..9ef18ee --- /dev/null +++ b/school/cpsc481/project2/src/algorithms/dfs/main.py @@ -0,0 +1,60 @@ +""" +Example implementation of DFS in Python + +Components include: + +X <- stores current state +open <- all nodes that we are allowed to push onto the stack +closed <- can no longer travel to + +Please refer to L02, part 2 at 12:45 for further details +""" + +graph = { + "A": ["B", "C"], + "B": ["D", "E"], + "C": ["X", "Y"], + "D": [], + "E": [], + "X": [], + "Y": [] +} + +def l_union(a: list, b: list): + # source : https://stackoverflow.com/questions/9792664/converting-a-list-to-a-set-changes-element-order + # find the union of two lists <- probably should use in Funnel + + if(not isinstance(a, list) or + not isinstance(b, list)): + raise ValueError('two lists please') + return [x for x in a if x not in b] + +# parameters have their desired types +def dfs(G: dict, S: list, GD: list) -> bool: # -> (TYPE BEING RETURNED) + # type enforcement + if(not isinstance(S, list) or + not isinstance(GD, list) or + not isinstance(G, dict)): + raise ValueError + + X = None + open_nodes = S # renaming + goal_nodes = GD # renaming + closed = [] + + while(open_nodes): # while len(open_nodes) > 0, python is cool + X = open_nodes.pop() + if(X in goal_nodes): + return True + children = l_union(graph[X], open_nodes) + open_nodes[:0] = children # prepend the contents of the lookup while removing duplicates from both containers + closed.append(X) # current node exhausted + + return False + +resultant = dfs(graph, ["A"], ["X"]) + +if(resultant): + print("[INFO] We found a result") +else: + print("[ERROR] We could not find a result") diff --git a/school/cpsc481/project2/src/autograder.py b/school/cpsc481/project2/src/autograder.py new file mode 100644 index 0000000..21f7d3b --- /dev/null +++ b/school/cpsc481/project2/src/autograder.py @@ -0,0 +1,358 @@ +# autograder.py +# ------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# imports from python standard library +import grading +import imp +import optparse +import os +import re +import sys +import projectParams +import random +random.seed(0) +try: + from pacman import GameState +except: + pass + +# register arguments and set default values +def readCommand(argv): + parser = optparse.OptionParser(description = 'Run public tests on student code') + parser.set_defaults(generateSolutions=False, edxOutput=False, gsOutput=False, muteOutput=False, printTestCase=False, noGraphics=False) + parser.add_option('--test-directory', + dest = 'testRoot', + default = 'test_cases', + help = 'Root test directory which contains subdirectories corresponding to each question') + parser.add_option('--student-code', + dest = 'studentCode', + default = projectParams.STUDENT_CODE_DEFAULT, + help = 'comma separated list of student code files') + parser.add_option('--code-directory', + dest = 'codeRoot', + default = "", + help = 'Root directory containing the student and testClass code') + parser.add_option('--test-case-code', + dest = 'testCaseCode', + default = projectParams.PROJECT_TEST_CLASSES, + help = 'class containing testClass classes for this project') + parser.add_option('--generate-solutions', + dest = 'generateSolutions', + action = 'store_true', + help = 'Write solutions generated to .solution file') + parser.add_option('--edx-output', + dest = 'edxOutput', + action = 'store_true', + help = 'Generate edX output files') + parser.add_option('--gradescope-output', + dest = 'gsOutput', + action = 'store_true', + help = 'Generate GradeScope output files') + parser.add_option('--mute', + dest = 'muteOutput', + action = 'store_true', + help = 'Mute output from executing tests') + parser.add_option('--print-tests', '-p', + dest = 'printTestCase', + action = 'store_true', + help = 'Print each test case before running them.') + parser.add_option('--test', '-t', + dest = 'runTest', + default = None, + help = 'Run one particular test. Relative to test root.') + parser.add_option('--question', '-q', + dest = 'gradeQuestion', + default = None, + help = 'Grade one particular question.') + parser.add_option('--no-graphics', + dest = 'noGraphics', + action = 'store_true', + help = 'No graphics display for pacman games.') + (options, args) = parser.parse_args(argv) + return options + + +# confirm we should author solution files +def confirmGenerate(): + print('WARNING: this action will overwrite any solution files.') + print('Are you sure you want to proceed? (yes/no)') + while True: + ans = sys.stdin.readline().strip() + if ans == 'yes': + break + elif ans == 'no': + sys.exit(0) + else: + print('please answer either "yes" or "no"') + + +# TODO: Fix this so that it tracebacks work correctly +# Looking at source of the traceback module, presuming it works +# the same as the intepreters, it uses co_filename. This is, +# however, a readonly attribute. +def setModuleName(module, filename): + functionType = type(confirmGenerate) + classType = type(optparse.Option) + + for i in dir(module): + o = getattr(module, i) + if hasattr(o, '__file__'): continue + + if type(o) == functionType: + setattr(o, '__file__', filename) + elif type(o) == classType: + setattr(o, '__file__', filename) + # TODO: assign member __file__'s? + #print(i, type(o)) + + +#from cStringIO import StringIO + +def loadModuleString(moduleSource): + # Below broken, imp doesn't believe its being passed a file: + # ValueError: load_module arg#2 should be a file or None + # + #f = StringIO(moduleCodeDict[k]) + #tmp = imp.load_module(k, f, k, (".py", "r", imp.PY_SOURCE)) + tmp = imp.new_module(k) + exec(moduleCodeDict[k] in tmp.__dict__) + setModuleName(tmp, k) + return tmp + +import py_compile + +def loadModuleFile(moduleName, filePath): + with open(filePath, 'r') as f: + return imp.load_module(moduleName, f, "%s.py" % moduleName, (".py", "r", imp.PY_SOURCE)) + + +def readFile(path, root=""): + "Read file from disk at specified path and return as string" + with open(os.path.join(root, path), 'r') as handle: + return handle.read() + + +####################################################################### +# Error Hint Map +####################################################################### + +# TODO: use these +ERROR_HINT_MAP = { + 'q1': { + "": """ + We noticed that your project threw an IndexError on q1. + While many things may cause this, it may have been from + assuming a certain number of successors from a state space + or assuming a certain number of actions available from a given + state. Try making your code more general (no hardcoded indices) + and submit again! + """ + }, + 'q3': { + "": """ + We noticed that your project threw an AttributeError on q3. + While many things may cause this, it may have been from assuming + a certain size or structure to the state space. For example, if you have + a line of code assuming that the state is (x, y) and we run your code + on a state space with (x, y, z), this error could be thrown. Try + making your code more general and submit again! + + """ + } +} + +import pprint + +def splitStrings(d): + d2 = dict(d) + for k in d: + if k[0:2] == "__": + del d2[k] + continue + if d2[k].find("\n") >= 0: + d2[k] = d2[k].split("\n") + return d2 + + +def printTest(testDict, solutionDict): + pp = pprint.PrettyPrinter(indent=4) + print("Test case:") + for line in testDict["__raw_lines__"]: + print(" |", line) + print("Solution:") + for line in solutionDict["__raw_lines__"]: + print(" |", line) + + +def runTest(testName, moduleDict, printTestCase=False, display=None): + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + testDict = testParser.TestParser(testName + ".test").parse() + solutionDict = testParser.TestParser(testName + ".solution").parse() + test_out_file = os.path.join('%s.test_output' % testName) + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + + questionClass = getattr(testClasses, 'Question') + question = questionClass({'max_points': 0}, display) + testCase = testClass(question, testDict) + + if printTestCase: + printTest(testDict, solutionDict) + + # This is a fragile hack to create a stub grades object + grades = grading.Grades(projectParams.PROJECT_NAME, [(None,0)]) + testCase.execute(grades, moduleDict, solutionDict) + + +# returns all the tests you need to run in order to run question +def getDepends(testParser, testRoot, question): + allDeps = [question] + questionDict = testParser.TestParser(os.path.join(testRoot, question, 'CONFIG')).parse() + if 'depends' in questionDict: + depends = questionDict['depends'].split() + for d in depends: + # run dependencies first + allDeps = getDepends(testParser, testRoot, d) + allDeps + return allDeps + +# get list of questions to grade +def getTestSubdirs(testParser, testRoot, questionToGrade): + problemDict = testParser.TestParser(os.path.join(testRoot, 'CONFIG')).parse() + if questionToGrade != None: + questions = getDepends(testParser, testRoot, questionToGrade) + if len(questions) > 1: + print('Note: due to dependencies, the following tests will be run: %s' % ' '.join(questions)) + return questions + if 'order' in problemDict: + return problemDict['order'].split() + return sorted(os.listdir(testRoot)) + + +# evaluate student code +def evaluate(generateSolutions, testRoot, moduleDict, exceptionMap=ERROR_HINT_MAP, + edxOutput=False, muteOutput=False, gsOutput=False, + printTestCase=False, questionToGrade=None, display=None): + # imports of testbench code. note that the testClasses import must follow + # the import of student code due to dependencies + import testParser + import testClasses + for module in moduleDict: + setattr(sys.modules[__name__], module, moduleDict[module]) + + questions = [] + questionDicts = {} + test_subdirs = getTestSubdirs(testParser, testRoot, questionToGrade) + for q in test_subdirs: + subdir_path = os.path.join(testRoot, q) + if not os.path.isdir(subdir_path) or q[0] == '.': + continue + + # create a question object + questionDict = testParser.TestParser(os.path.join(subdir_path, 'CONFIG')).parse() + questionClass = getattr(testClasses, questionDict['class']) + question = questionClass(questionDict, display) + questionDicts[q] = questionDict + + # load test cases into question + tests = filter(lambda t: re.match('[^#~.].*\.test\Z', t), os.listdir(subdir_path)) + tests = map(lambda t: re.match('(.*)\.test\Z', t).group(1), tests) + for t in sorted(tests): + test_file = os.path.join(subdir_path, '%s.test' % t) + solution_file = os.path.join(subdir_path, '%s.solution' % t) + test_out_file = os.path.join(subdir_path, '%s.test_output' % t) + testDict = testParser.TestParser(test_file).parse() + if testDict.get("disabled", "false").lower() == "true": + continue + testDict['test_out_file'] = test_out_file + testClass = getattr(projectTestClasses, testDict['class']) + testCase = testClass(question, testDict) + def makefun(testCase, solution_file): + if generateSolutions: + # write solution file to disk + return lambda grades: testCase.writeSolution(moduleDict, solution_file) + else: + # read in solution dictionary and pass as an argument + testDict = testParser.TestParser(test_file).parse() + solutionDict = testParser.TestParser(solution_file).parse() + if printTestCase: + return lambda grades: printTest(testDict, solutionDict) or testCase.execute(grades, moduleDict, solutionDict) + else: + return lambda grades: testCase.execute(grades, moduleDict, solutionDict) + question.addTestCase(testCase, makefun(testCase, solution_file)) + + # Note extra function is necessary for scoping reasons + def makefun(question): + return lambda grades: question.execute(grades) + setattr(sys.modules[__name__], q, makefun(question)) + questions.append((q, question.getMaxPoints())) + + grades = grading.Grades(projectParams.PROJECT_NAME, questions, + gsOutput=gsOutput, edxOutput=edxOutput, muteOutput=muteOutput) + if questionToGrade == None: + for q in questionDicts: + for prereq in questionDicts[q].get('depends', '').split(): + grades.addPrereq(q, prereq) + + grades.grade(sys.modules[__name__], bonusPic = projectParams.BONUS_PIC) + return grades.points + + + +def getDisplay(graphicsByDefault, options=None): + graphics = graphicsByDefault + if options is not None and options.noGraphics: + graphics = False + if graphics: + try: + import graphicsDisplay + return graphicsDisplay.PacmanGraphics(1, frameTime=.05) + except ImportError: + pass + import textDisplay + return textDisplay.NullGraphics() + + + + +if __name__ == '__main__': + options = readCommand(sys.argv) + if options.generateSolutions: + confirmGenerate() + codePaths = options.studentCode.split(',') + # moduleCodeDict = {} + # for cp in codePaths: + # moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + # moduleCodeDict[moduleName] = readFile(cp, root=options.codeRoot) + # moduleCodeDict['projectTestClasses'] = readFile(options.testCaseCode, root=options.codeRoot) + # moduleDict = loadModuleDict(moduleCodeDict) + + moduleDict = {} + for cp in codePaths: + moduleName = re.match('.*?([^/]*)\.py', cp).group(1) + moduleDict[moduleName] = loadModuleFile(moduleName, os.path.join(options.codeRoot, cp)) + moduleName = re.match('.*?([^/]*)\.py', options.testCaseCode).group(1) + moduleDict['projectTestClasses'] = loadModuleFile(moduleName, os.path.join(options.codeRoot, options.testCaseCode)) + + + if options.runTest != None: + runTest(options.runTest, moduleDict, printTestCase=options.printTestCase, display=getDisplay(True, options)) + else: + evaluate(options.generateSolutions, options.testRoot, moduleDict, + gsOutput=options.gsOutput, + edxOutput=options.edxOutput, muteOutput=options.muteOutput, printTestCase=options.printTestCase, + questionToGrade=options.gradeQuestion, display=getDisplay(options.gradeQuestion!=None, options)) diff --git a/school/cpsc481/project2/src/commands.txt b/school/cpsc481/project2/src/commands.txt new file mode 100644 index 0000000..21683ac --- /dev/null +++ b/school/cpsc481/project2/src/commands.txt @@ -0,0 +1,21 @@ +python pacman.py +python pacman.py --layout testMaze --pacman GoWestAgent +python pacman.py --layout tinyMaze --pacman GoWestAgent +python pacman.py -h +python pacman.py -l tinyMaze -p SearchAgent -a fn=tinyMazeSearch +python pacman.py -l tinyMaze -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent +python pacman.py -l mediumMaze -p SearchAgent -a fn=bfs +python pacman.py -l bigMaze -p SearchAgent -a fn=bfs -z .5 +python eightpuzzle.py +python pacman.py -l mediumMaze -p SearchAgent -a fn=ucs +python pacman.py -l mediumDottedMaze -p StayEastSearchAgent +python pacman.py -l mediumScaryMaze -p StayWestSearchAgent +python pacman.py -l bigMaze -z .5 -p SearchAgent -a fn=astar,heuristic=manhattanHeuristic +python pacman.py -l tinyCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p SearchAgent -a fn=bfs,prob=CornersProblem +python pacman.py -l mediumCorners -p AStarCornersAgent -z 0.5 +python pacman.py -l testSearch -p AStarFoodSearchAgent +python pacman.py -l trickySearch -p AStarFoodSearchAgent +python pacman.py -l bigSearch -p ClosestDotSearchAgent -z .5 diff --git a/school/cpsc481/project2/src/eightpuzzle.py b/school/cpsc481/project2/src/eightpuzzle.py new file mode 100644 index 0000000..bb8ea6f --- /dev/null +++ b/school/cpsc481/project2/src/eightpuzzle.py @@ -0,0 +1,281 @@ +# eightpuzzle.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +import search +import random + +# Module Classes + +class EightPuzzleState: + """ + The Eight Puzzle is described in the course textbook on + page 64. + + This class defines the mechanics of the puzzle itself. The + task of recasting this puzzle as a search problem is left to + the EightPuzzleSearchProblem class. + """ + + def __init__( self, numbers ): + """ + Constructs a new eight puzzle from an ordering of numbers. + + numbers: a list of integers from 0 to 8 representing an + instance of the eight puzzle. 0 represents the blank + space. Thus, the list + + [1, 0, 2, 3, 4, 5, 6, 7, 8] + + represents the eight puzzle: + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------ + + The configuration of the puzzle is stored in a 2-dimensional + list (a list of lists) 'cells'. + """ + self.cells = [] + numbers = numbers[:] # Make a copy so as not to cause side-effects. + numbers.reverse() + for row in range( 3 ): + self.cells.append( [] ) + for col in range( 3 ): + self.cells[row].append( numbers.pop() ) + if self.cells[row][col] == 0: + self.blankLocation = row, col + + def isGoal( self ): + """ + Checks to see if the puzzle is in its goal state. + + ------------- + | | 1 | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).isGoal() + True + + >>> EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).isGoal() + False + """ + current = 0 + for row in range( 3 ): + for col in range( 3 ): + if current != self.cells[row][col]: + return False + current += 1 + return True + + def legalMoves( self ): + """ + Returns a list of legal moves from the current state. + + Moves consist of moving the blank space up, down, left or right. + These are encoded as 'up', 'down', 'left' and 'right' respectively. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]).legalMoves() + ['down', 'right'] + """ + moves = [] + row, col = self.blankLocation + if(row != 0): + moves.append('up') + if(row != 2): + moves.append('down') + if(col != 0): + moves.append('left') + if(col != 2): + moves.append('right') + return moves + + def result(self, move): + """ + Returns a new eightPuzzle with the current state and blankLocation + updated based on the provided move. + + The move should be a string drawn from a list returned by legalMoves. + Illegal moves will raise an exception, which may be an array bounds + exception. + + NOTE: This function *does not* change the current object. Instead, + it returns a new object. + """ + row, col = self.blankLocation + if(move == 'up'): + newrow = row - 1 + newcol = col + elif(move == 'down'): + newrow = row + 1 + newcol = col + elif(move == 'left'): + newrow = row + newcol = col - 1 + elif(move == 'right'): + newrow = row + newcol = col + 1 + else: + raise "Illegal Move" + + # Create a copy of the current eightPuzzle + newPuzzle = EightPuzzleState([0, 0, 0, 0, 0, 0, 0, 0, 0]) + newPuzzle.cells = [values[:] for values in self.cells] + # And update it to reflect the move + newPuzzle.cells[row][col] = self.cells[newrow][newcol] + newPuzzle.cells[newrow][newcol] = self.cells[row][col] + newPuzzle.blankLocation = newrow, newcol + + return newPuzzle + + # Utilities for comparison and display + def __eq__(self, other): + """ + Overloads '==' such that two eightPuzzles with the same configuration + are equal. + + >>> EightPuzzleState([0, 1, 2, 3, 4, 5, 6, 7, 8]) == \ + EightPuzzleState([1, 0, 2, 3, 4, 5, 6, 7, 8]).result('left') + True + """ + for row in range( 3 ): + if self.cells[row] != other.cells[row]: + return False + return True + + def __hash__(self): + return hash(str(self.cells)) + + def __getAsciiString(self): + """ + Returns a display string for the maze + """ + lines = [] + horizontalLine = ('-' * (13)) + lines.append(horizontalLine) + for row in self.cells: + rowLine = '|' + for col in row: + if col == 0: + col = ' ' + rowLine = rowLine + ' ' + col.__str__() + ' |' + lines.append(rowLine) + lines.append(horizontalLine) + return '\n'.join(lines) + + def __str__(self): + return self.__getAsciiString() + +# TODO: Implement The methods in this class + +class EightPuzzleSearchProblem(search.SearchProblem): + """ + Implementation of a SearchProblem for the Eight Puzzle domain + + Each state is represented by an instance of an eightPuzzle. + """ + def __init__(self,puzzle): + "Creates a new EightPuzzleSearchProblem which stores search information." + self.puzzle = puzzle + + def getStartState(self): + return puzzle + + def isGoalState(self,state): + return state.isGoal() + + def getSuccessors(self,state): + """ + Returns list of (successor, action, stepCost) pairs where + each succesor is either left, right, up, or down + from the original state and the cost is 1.0 for each + """ + succ = [] + for a in state.legalMoves(): + succ.append((state.result(a), a, 1)) + return succ + + def getCostOfActions(self, actions): + """ + actions: A list of actions to take + + This method returns the total cost of a particular sequence of actions. The sequence must + be composed of legal moves + """ + return len(actions) + +EIGHT_PUZZLE_DATA = [[1, 0, 2, 3, 4, 5, 6, 7, 8], + [1, 7, 8, 2, 3, 4, 5, 6, 0], + [4, 3, 2, 7, 0, 5, 1, 6, 8], + [5, 1, 3, 4, 0, 2, 6, 7, 8], + [1, 2, 5, 7, 6, 8, 0, 4, 3], + [0, 3, 1, 6, 8, 2, 7, 5, 4]] + +def loadEightPuzzle(puzzleNumber): + """ + puzzleNumber: The number of the eight puzzle to load. + + Returns an eight puzzle object generated from one of the + provided puzzles in EIGHT_PUZZLE_DATA. + + puzzleNumber can range from 0 to 5. + + >>> print(loadEightPuzzle(0)) + ------------- + | 1 | | 2 | + ------------- + | 3 | 4 | 5 | + ------------- + | 6 | 7 | 8 | + ------------- + """ + return EightPuzzleState(EIGHT_PUZZLE_DATA[puzzleNumber]) + +def createRandomEightPuzzle(moves=100): + """ + moves: number of random moves to apply + + Creates a random eight puzzle by applying + a series of 'moves' random moves to a solved + puzzle. + """ + puzzle = EightPuzzleState([0,1,2,3,4,5,6,7,8]) + for i in range(moves): + # Execute a random legal move + puzzle = puzzle.result(random.sample(puzzle.legalMoves(), 1)[0]) + return puzzle + +if __name__ == '__main__': + puzzle = createRandomEightPuzzle(25) + print('A random puzzle:') + print(puzzle) + + problem = EightPuzzleSearchProblem(puzzle) + path = search.breadthFirstSearch(problem) + print('BFS found a path of %d moves: %s' % (len(path), str(path))) + curr = puzzle + i = 1 + for a in path: + curr = curr.result(a) + print('After %d move%s: %s' % (i, ("", "s")[i>1], a)) + print(curr) + + input("Press return for the next state...") # wait for key stroke + i += 1 diff --git a/school/cpsc481/project2/src/game.py b/school/cpsc481/project2/src/game.py new file mode 100644 index 0000000..3950f27 --- /dev/null +++ b/school/cpsc481/project2/src/game.py @@ -0,0 +1,729 @@ +# game.py +# ------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +# game.py +# ------- +# Licensing Information: Please do not distribute or publish solutions to this +# project. You are free to use and extend these projects for educational +# purposes. The Pacman AI projects were developed at UC Berkeley, primarily by +# John DeNero (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# For more info, see http://inst.eecs.berkeley.edu/~cs188/sp09/pacman.html + +from util import * +import time, os +import traceback +import sys + +####################### +# Parts worth reading # +####################### + +class Agent: + """ + An agent must define a getAction method, but may also define the + following methods which will be called if they exist: + + def registerInitialState(self, state): # inspects the starting state + """ + def __init__(self, index=0): + self.index = index + + def getAction(self, state): + """ + The Agent will receive a GameState (from either {pacman, capture, sonar}.py) and + must return an action from Directions.{North, South, East, West, Stop} + """ + raiseNotDefined() + +class Directions: + NORTH = 'North' + SOUTH = 'South' + EAST = 'East' + WEST = 'West' + STOP = 'Stop' + + LEFT = {NORTH: WEST, + SOUTH: EAST, + EAST: NORTH, + WEST: SOUTH, + STOP: STOP} + + RIGHT = dict([(y,x) for x, y in LEFT.items()]) + + REVERSE = {NORTH: SOUTH, + SOUTH: NORTH, + EAST: WEST, + WEST: EAST, + STOP: STOP} + +class Configuration: + """ + A Configuration holds the (x,y) coordinate of a character, along with its + traveling direction. + + The convention for positions, like a graph, is that (0,0) is the lower left corner, x increases + horizontally and y increases vertically. Therefore, north is the direction of increasing y, or (0,1). + """ + + def __init__(self, pos, direction): + self.pos = pos + self.direction = direction + + def getPosition(self): + return (self.pos) + + def getDirection(self): + return self.direction + + def isInteger(self): + x,y = self.pos + return x == int(x) and y == int(y) + + def __eq__(self, other): + if other == None: return False + return (self.pos == other.pos and self.direction == other.direction) + + def __hash__(self): + x = hash(self.pos) + y = hash(self.direction) + return hash(x + 13 * y) + + def __str__(self): + return "(x,y)="+str(self.pos)+", "+str(self.direction) + + def generateSuccessor(self, vector): + """ + Generates a new configuration reached by translating the current + configuration by the action vector. This is a low-level call and does + not attempt to respect the legality of the movement. + + Actions are movement vectors. + """ + x, y= self.pos + dx, dy = vector + direction = Actions.vectorToDirection(vector) + if direction == Directions.STOP: + direction = self.direction # There is no stop direction + return Configuration((x + dx, y+dy), direction) + +class AgentState: + """ + AgentStates hold the state of an agent (configuration, speed, scared, etc). + """ + + def __init__( self, startConfiguration, isPacman ): + self.start = startConfiguration + self.configuration = startConfiguration + self.isPacman = isPacman + self.scaredTimer = 0 + self.numCarrying = 0 + self.numReturned = 0 + + def __str__( self ): + if self.isPacman: + return "Pacman: " + str( self.configuration ) + else: + return "Ghost: " + str( self.configuration ) + + def __eq__( self, other ): + if other == None: + return False + return self.configuration == other.configuration and self.scaredTimer == other.scaredTimer + + def __hash__(self): + return hash(hash(self.configuration) + 13 * hash(self.scaredTimer)) + + def copy( self ): + state = AgentState( self.start, self.isPacman ) + state.configuration = self.configuration + state.scaredTimer = self.scaredTimer + state.numCarrying = self.numCarrying + state.numReturned = self.numReturned + return state + + def getPosition(self): + if self.configuration == None: return None + return self.configuration.getPosition() + + def getDirection(self): + return self.configuration.getDirection() + +class Grid: + """ + A 2-dimensional array of objects backed by a list of lists. Data is accessed + via grid[x][y] where (x,y) are positions on a Pacman map with x horizontal, + y vertical and the origin (0,0) in the bottom left corner. + + The __str__ method constructs an output that is oriented like a pacman board. + """ + def __init__(self, width, height, initialValue=False, bitRepresentation=None): + if initialValue not in [False, True]: raise Exception('Grids can only contain booleans') + self.CELLS_PER_INT = 30 + + self.width = width + self.height = height + self.data = [[initialValue for y in range(height)] for x in range(width)] + if bitRepresentation: + self._unpackBits(bitRepresentation) + + def __getitem__(self, i): + return self.data[i] + + def __setitem__(self, key, item): + self.data[key] = item + + def __str__(self): + out = [[str(self.data[x][y])[0] for x in range(self.width)] for y in range(self.height)] + out.reverse() + return '\n'.join([''.join(x) for x in out]) + + def __eq__(self, other): + if other == None: return False + return self.data == other.data + + def __hash__(self): + # return hash(str(self)) + base = 1 + h = 0 + for l in self.data: + for i in l: + if i: + h += base + base *= 2 + return hash(h) + + def copy(self): + g = Grid(self.width, self.height) + g.data = [x[:] for x in self.data] + return g + + def deepCopy(self): + return self.copy() + + def shallowCopy(self): + g = Grid(self.width, self.height) + g.data = self.data + return g + + def count(self, item =True ): + return sum([x.count(item) for x in self.data]) + + def asList(self, key = True): + list = [] + for x in range(self.width): + for y in range(self.height): + if self[x][y] == key: list.append( (x,y) ) + return list + + def packBits(self): + """ + Returns an efficient int list representation + + (width, height, bitPackedInts...) + """ + bits = [self.width, self.height] + currentInt = 0 + for i in range(self.height * self.width): + bit = self.CELLS_PER_INT - (i % self.CELLS_PER_INT) - 1 + x, y = self._cellIndexToPosition(i) + if self[x][y]: + currentInt += 2 ** bit + if (i + 1) % self.CELLS_PER_INT == 0: + bits.append(currentInt) + currentInt = 0 + bits.append(currentInt) + return tuple(bits) + + def _cellIndexToPosition(self, index): + x = index // self.height + y = index % self.height + return x, y + + def _unpackBits(self, bits): + """ + Fills in data from a bit-level representation + """ + cell = 0 + for packed in bits: + for bit in self._unpackInt(packed, self.CELLS_PER_INT): + if cell == self.width * self.height: break + x, y = self._cellIndexToPosition(cell) + self[x][y] = bit + cell += 1 + + def _unpackInt(self, packed, size): + bools = [] + if packed < 0: raise ValueError("must be a positive integer") + for i in range(size): + n = 2 ** (self.CELLS_PER_INT - i - 1) + if packed >= n: + bools.append(True) + packed -= n + else: + bools.append(False) + return bools + +def reconstituteGrid(bitRep): + if type(bitRep) is not type((1,2)): + return bitRep + width, height = bitRep[:2] + return Grid(width, height, bitRepresentation= bitRep[2:]) + +#################################### +# Parts you shouldn't have to read # +#################################### + +class Actions: + """ + A collection of static methods for manipulating move actions. + """ + # Directions + _directions = {Directions.NORTH: (0, 1), + Directions.SOUTH: (0, -1), + Directions.EAST: (1, 0), + Directions.WEST: (-1, 0), + Directions.STOP: (0, 0)} + + _directionsAsList = _directions.items() + + TOLERANCE = .001 + + def reverseDirection(action): + if action == Directions.NORTH: + return Directions.SOUTH + if action == Directions.SOUTH: + return Directions.NORTH + if action == Directions.EAST: + return Directions.WEST + if action == Directions.WEST: + return Directions.EAST + return action + reverseDirection = staticmethod(reverseDirection) + + def vectorToDirection(vector): + dx, dy = vector + if dy > 0: + return Directions.NORTH + if dy < 0: + return Directions.SOUTH + if dx < 0: + return Directions.WEST + if dx > 0: + return Directions.EAST + return Directions.STOP + vectorToDirection = staticmethod(vectorToDirection) + + def directionToVector(direction, speed = 1.0): + dx, dy = Actions._directions[direction] + return (dx * speed, dy * speed) + directionToVector = staticmethod(directionToVector) + + def getPossibleActions(config, walls): + possible = [] + x, y = config.pos + x_int, y_int = int(x + 0.5), int(y + 0.5) + + # In between grid points, all agents must continue straight + if (abs(x - x_int) + abs(y - y_int) > Actions.TOLERANCE): + return [config.getDirection()] + + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_y = y_int + dy + next_x = x_int + dx + if not walls[next_x][next_y]: possible.append(dir) + + return possible + + getPossibleActions = staticmethod(getPossibleActions) + + def getLegalNeighbors(position, walls): + x,y = position + x_int, y_int = int(x + 0.5), int(y + 0.5) + neighbors = [] + for dir, vec in Actions._directionsAsList: + dx, dy = vec + next_x = x_int + dx + if next_x < 0 or next_x == walls.width: continue + next_y = y_int + dy + if next_y < 0 or next_y == walls.height: continue + if not walls[next_x][next_y]: neighbors.append((next_x, next_y)) + return neighbors + getLegalNeighbors = staticmethod(getLegalNeighbors) + + def getSuccessor(position, action): + dx, dy = Actions.directionToVector(action) + x, y = position + return (x + dx, y + dy) + getSuccessor = staticmethod(getSuccessor) + +class GameStateData: + """ + + """ + def __init__( self, prevState = None ): + """ + Generates a new data packet by copying information from its predecessor. + """ + if prevState != None: + self.food = prevState.food.shallowCopy() + self.capsules = prevState.capsules[:] + self.agentStates = self.copyAgentStates( prevState.agentStates ) + self.layout = prevState.layout + self._eaten = prevState._eaten + self.score = prevState.score + + self._foodEaten = None + self._foodAdded = None + self._capsuleEaten = None + self._agentMoved = None + self._lose = False + self._win = False + self.scoreChange = 0 + + def deepCopy( self ): + state = GameStateData( self ) + state.food = self.food.deepCopy() + state.layout = self.layout.deepCopy() + state._agentMoved = self._agentMoved + state._foodEaten = self._foodEaten + state._foodAdded = self._foodAdded + state._capsuleEaten = self._capsuleEaten + return state + + def copyAgentStates( self, agentStates ): + copiedStates = [] + for agentState in agentStates: + copiedStates.append( agentState.copy() ) + return copiedStates + + def __eq__( self, other ): + """ + Allows two states to be compared. + """ + if other == None: return False + # TODO Check for type of other + if not self.agentStates == other.agentStates: return False + if not self.food == other.food: return False + if not self.capsules == other.capsules: return False + if not self.score == other.score: return False + return True + + def __hash__( self ): + """ + Allows states to be keys of dictionaries. + """ + for i, state in enumerate( self.agentStates ): + try: + int(hash(state)) + except TypeError as e: + print(e) + #hash(state) + return int((hash(tuple(self.agentStates)) + 13*hash(self.food) + 113* hash(tuple(self.capsules)) + 7 * hash(self.score)) % 1048575 ) + + def __str__( self ): + width, height = self.layout.width, self.layout.height + map = Grid(width, height) + if type(self.food) == type((1,2)): + self.food = reconstituteGrid(self.food) + for x in range(width): + for y in range(height): + food, walls = self.food, self.layout.walls + map[x][y] = self._foodWallStr(food[x][y], walls[x][y]) + + for agentState in self.agentStates: + if agentState == None: continue + if agentState.configuration == None: continue + x,y = [int( i ) for i in nearestPoint( agentState.configuration.pos )] + agent_dir = agentState.configuration.direction + if agentState.isPacman: + map[x][y] = self._pacStr( agent_dir ) + else: + map[x][y] = self._ghostStr( agent_dir ) + + for x, y in self.capsules: + map[x][y] = 'o' + + return str(map) + ("\nScore: %d\n" % self.score) + + def _foodWallStr( self, hasFood, hasWall ): + if hasFood: + return '.' + elif hasWall: + return '%' + else: + return ' ' + + def _pacStr( self, dir ): + if dir == Directions.NORTH: + return 'v' + if dir == Directions.SOUTH: + return '^' + if dir == Directions.WEST: + return '>' + return '<' + + def _ghostStr( self, dir ): + return 'G' + if dir == Directions.NORTH: + return 'M' + if dir == Directions.SOUTH: + return 'W' + if dir == Directions.WEST: + return '3' + return 'E' + + def initialize( self, layout, numGhostAgents ): + """ + Creates an initial game state from a layout array (see layout.py). + """ + self.food = layout.food.copy() + #self.capsules = [] + self.capsules = layout.capsules[:] + self.layout = layout + self.score = 0 + self.scoreChange = 0 + + self.agentStates = [] + numGhosts = 0 + for isPacman, pos in layout.agentPositions: + if not isPacman: + if numGhosts == numGhostAgents: continue # Max ghosts reached already + else: numGhosts += 1 + self.agentStates.append( AgentState( Configuration( pos, Directions.STOP), isPacman) ) + self._eaten = [False for a in self.agentStates] + +try: + import boinc + _BOINC_ENABLED = True +except: + _BOINC_ENABLED = False + +class Game: + """ + The Game manages the control flow, soliciting actions from agents. + """ + + def __init__( self, agents, display, rules, startingIndex=0, muteAgents=False, catchExceptions=False ): + self.agentCrashed = False + self.agents = agents + self.display = display + self.rules = rules + self.startingIndex = startingIndex + self.gameOver = False + self.muteAgents = muteAgents + self.catchExceptions = catchExceptions + self.moveHistory = [] + self.totalAgentTimes = [0 for agent in agents] + self.totalAgentTimeWarnings = [0 for agent in agents] + self.agentTimeout = False + import io + self.agentOutput = [io.StringIO() for agent in agents] + + def getProgress(self): + if self.gameOver: + return 1.0 + else: + return self.rules.getProgress(self) + + def _agentCrash( self, agentIndex, quiet=False): + "Helper method for handling agent crashes" + if not quiet: traceback.print_exc() + self.gameOver = True + self.agentCrashed = True + self.rules.agentCrash(self, agentIndex) + + OLD_STDOUT = None + OLD_STDERR = None + + def mute(self, agentIndex): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + import io + OLD_STDOUT = sys.stdout + OLD_STDERR = sys.stderr + sys.stdout = self.agentOutput[agentIndex] + sys.stderr = self.agentOutput[agentIndex] + + def unmute(self): + if not self.muteAgents: return + global OLD_STDOUT, OLD_STDERR + # Revert stdout/stderr to originals + sys.stdout = OLD_STDOUT + sys.stderr = OLD_STDERR + + + def run( self ): + """ + Main control loop for game play. + """ + self.display.initialize(self.state.data) + self.numMoves = 0 + + ###self.display.initialize(self.state.makeObservation(1).data) + # inform learning agents of the game start + for i in range(len(self.agents)): + agent = self.agents[i] + if not agent: + self.mute(i) + # this is a null agent, meaning it failed to load + # the other team wins + print("Agent %d failed to load" % i, file=sys.stderr) + self.unmute() + self._agentCrash(i, quiet=True) + return + if ("registerInitialState" in dir(agent)): + self.mute(i) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.registerInitialState, int(self.rules.getMaxStartupTime(i))) + try: + start_time = time.time() + timed_func(self.state.deepCopy()) + time_taken = time.time() - start_time + self.totalAgentTimes[i] += time_taken + except TimeoutFunctionException: + print("Agent %d ran out of time on startup!" % i, file=sys.stderr) + self.unmute() + self.agentTimeout = True + self._agentCrash(i, quiet=True) + return + except Exception as data: + self._agentCrash(i, quiet=False) + self.unmute() + return + else: + agent.registerInitialState(self.state.deepCopy()) + ## TODO: could this exceed the total time + self.unmute() + + agentIndex = self.startingIndex + numAgents = len( self.agents ) + + while not self.gameOver: + # Fetch the next agent + agent = self.agents[agentIndex] + move_time = 0 + skip_action = False + # Generate an observation of the state + if 'observationFunction' in dir( agent ): + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.observationFunction, int(self.rules.getMoveTimeout(agentIndex))) + try: + start_time = time.time() + observation = timed_func(self.state.deepCopy()) + except TimeoutFunctionException: + skip_action = True + move_time += time.time() - start_time + self.unmute() + except Exception as data: + self._agentCrash(agentIndex, quiet=False) + self.unmute() + return + else: + observation = agent.observationFunction(self.state.deepCopy()) + self.unmute() + else: + observation = self.state.deepCopy() + + # Solicit an action + action = None + self.mute(agentIndex) + if self.catchExceptions: + try: + timed_func = TimeoutFunction(agent.getAction, int(self.rules.getMoveTimeout(agentIndex)) - int(move_time)) + try: + start_time = time.time() + if skip_action: + raise TimeoutFunctionException() + action = timed_func( observation ) + except TimeoutFunctionException: + print("Agent %d timed out on a single move!" % agentIndex, file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + move_time += time.time() - start_time + + if move_time > self.rules.getMoveWarningTime(agentIndex): + self.totalAgentTimeWarnings[agentIndex] += 1 + print("Agent %d took too long to make a move! This is warning %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + if self.totalAgentTimeWarnings[agentIndex] > self.rules.getMaxTimeWarnings(agentIndex): + print("Agent %d exceeded the maximum number of warnings: %d" % (agentIndex, self.totalAgentTimeWarnings[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + + self.totalAgentTimes[agentIndex] += move_time + #print("Agent: %d, time: %f, total: %f" % (agentIndex, move_time, self.totalAgentTimes[agentIndex])) + if self.totalAgentTimes[agentIndex] > self.rules.getMaxTotalTime(agentIndex): + print("Agent %d ran out of time! (time: %1.2f)" % (agentIndex, self.totalAgentTimes[agentIndex]), file=sys.stderr) + self.agentTimeout = True + self._agentCrash(agentIndex, quiet=True) + self.unmute() + return + self.unmute() + except Exception as data: + self._agentCrash(agentIndex) + self.unmute() + return + else: + action = agent.getAction(observation) + self.unmute() + + # Execute the action + self.moveHistory.append( (agentIndex, action) ) + if self.catchExceptions: + try: + self.state = self.state.generateSuccessor( agentIndex, action ) + except Exception as data: + self.mute(agentIndex) + self._agentCrash(agentIndex) + self.unmute() + return + else: + self.state = self.state.generateSuccessor( agentIndex, action ) + + # Change the display + self.display.update( self.state.data ) + ###idx = agentIndex - agentIndex % 2 + 1 + ###self.display.update( self.state.makeObservation(idx).data ) + + # Allow for game specific conditions (winning, losing, etc.) + self.rules.process(self.state, self) + # Track progress + if agentIndex == numAgents + 1: self.numMoves += 1 + # Next agent + agentIndex = ( agentIndex + 1 ) % numAgents + + if _BOINC_ENABLED: + boinc.set_fraction_done(self.getProgress()) + + # inform a learning agent of the game result + for agentIndex, agent in enumerate(self.agents): + if "final" in dir( agent ) : + try: + self.mute(agentIndex) + agent.final( self.state ) + self.unmute() + except Exception as data: + if not self.catchExceptions: raise data + self._agentCrash(agentIndex) + self.unmute() + return + self.display.finish() diff --git a/school/cpsc481/project2/src/ghostAgents.py b/school/cpsc481/project2/src/ghostAgents.py new file mode 100644 index 0000000..c3afe1f --- /dev/null +++ b/school/cpsc481/project2/src/ghostAgents.py @@ -0,0 +1,81 @@ +# ghostAgents.py +# -------------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +from game import Agent +from game import Actions +from game import Directions +import random +from util import manhattanDistance +import util + +class GhostAgent( Agent ): + def __init__( self, index ): + self.index = index + + def getAction( self, state ): + dist = self.getDistribution(state) + if len(dist) == 0: + return Directions.STOP + else: + return util.chooseFromDistribution( dist ) + + def getDistribution(self, state): + "Returns a Counter encoding a distribution over actions from the provided state." + util.raiseNotDefined() + +class RandomGhost( GhostAgent ): + "A ghost that chooses a legal action uniformly at random." + def getDistribution( self, state ): + dist = util.Counter() + for a in state.getLegalActions( self.index ): dist[a] = 1.0 + dist.normalize() + return dist + +class DirectionalGhost( GhostAgent ): + "A ghost that prefers to rush Pacman, or flee when scared." + def __init__( self, index, prob_attack=0.8, prob_scaredFlee=0.8 ): + self.index = index + self.prob_attack = prob_attack + self.prob_scaredFlee = prob_scaredFlee + + def getDistribution( self, state ): + # Read variables from state + ghostState = state.getGhostState( self.index ) + legalActions = state.getLegalActions( self.index ) + pos = state.getGhostPosition( self.index ) + isScared = ghostState.scaredTimer > 0 + + speed = 1 + if isScared: speed = 0.5 + + actionVectors = [Actions.directionToVector( a, speed ) for a in legalActions] + newPositions = [( pos[0]+a[0], pos[1]+a[1] ) for a in actionVectors] + pacmanPosition = state.getPacmanPosition() + + # Select best actions given the state + distancesToPacman = [manhattanDistance( pos, pacmanPosition ) for pos in newPositions] + if isScared: + bestScore = max( distancesToPacman ) + bestProb = self.prob_scaredFlee + else: + bestScore = min( distancesToPacman ) + bestProb = self.prob_attack + bestActions = [action for action, distance in zip( legalActions, distancesToPacman ) if distance == bestScore] + + # Construct distribution + dist = util.Counter() + for a in bestActions: dist[a] = bestProb / len(bestActions) + for a in legalActions: dist[a] += ( 1-bestProb ) / len(legalActions) + dist.normalize() + return dist diff --git a/school/cpsc481/project2/src/grading.py b/school/cpsc481/project2/src/grading.py new file mode 100644 index 0000000..6384a56 --- /dev/null +++ b/school/cpsc481/project2/src/grading.py @@ -0,0 +1,323 @@ +# grading.py +# ---------- +# Licensing Information: You are free to use or extend these projects for +# educational purposes provided that (1) you do not distribute or publish +# solutions, (2) you retain this notice, and (3) you provide clear +# attribution to UC Berkeley, including a link to http://ai.berkeley.edu. +# +# Attribution Information: The Pacman AI projects were developed at UC Berkeley. +# The core projects and autograders were primarily created by John DeNero +# (denero@cs.berkeley.edu) and Dan Klein (klein@cs.berkeley.edu). +# Student side autograding was added by Brad Miller, Nick Hay, and +# Pieter Abbeel (pabbeel@cs.berkeley.edu). + + +"Common code for autograders" + +import cgi +import time +import sys +import json +import traceback +import pdb +from collections import defaultdict +import util + +class Grades: + "A data structure for project grades, along with formatting code to display them" + def __init__(self, projectName, questionsAndMaxesList, + gsOutput=False, edxOutput=False, muteOutput=False): + """ + Defines the grading scheme for a project + projectName: project name + questionsAndMaxesDict: a list of (question name, max points per question) + """ + self.questions = [el[0] for el in questionsAndMaxesList] + self.maxes = dict(questionsAndMaxesList) + self.points = Counter() + self.messages = dict([(q, []) for q in self.questions]) + self.project = projectName + self.start = time.localtime()[1:6] + self.sane = True # Sanity checks + self.currentQuestion = None # Which question we're grading + self.edxOutput = edxOutput + self.gsOutput = gsOutput # GradeScope output + self.mute = muteOutput + self.prereqs = defaultdict(set) + + #print('Autograder transcript for %s' % self.project) + print('Starting on %d-%d at %d:%02d:%02d' % self.start) + + def addPrereq(self, question, prereq): + self.prereqs[question].add(prereq) + + def grade(self, gradingModule, exceptionMap = {}, bonusPic = False): + """ + Grades each question + gradingModule: the module with all the grading functions (pass in with sys.modules[__name__]) + """ + + completedQuestions = set([]) + for q in self.questions: + print('\nQuestion %s' % q) + print('=' * (9 + len(q))) + print + self.currentQuestion = q + + incompleted = self.prereqs[q].difference(completedQuestions) + if len(incompleted) > 0: + prereq = incompleted.pop() + print( +"""*** NOTE: Make sure to complete Question %s before working on Question %s, +*** because Question %s builds upon your answer for Question %s. +""" % (prereq, q, q, prereq)) + continue + + if self.mute: util.mutePrint() + try: + util.TimeoutFunction(getattr(gradingModule, q),1800)(self) # Call the question's function + #TimeoutFunction(getattr(gradingModule, q),1200)(self) # Call the question's function + except Exception as inst: + self.addExceptionMessage(q, inst, traceback) + self.addErrorHints(exceptionMap, inst, q[1]) + except: + self.fail('FAIL: Terminated with a string exception.') + finally: + if self.mute: util.unmutePrint() + + if self.points[q] >= self.maxes[q]: + completedQuestions.add(q) + + print('\n### Question %s: %d/%d ###\n' % (q, self.points[q], self.maxes[q])) + + + print('\nFinished at %d:%02d:%02d' % time.localtime()[3:6]) + print("\nProvisional grades\n==================") + + for q in self.questions: + print('Question %s: %d/%d' % (q, self.points[q], self.maxes[q])) + print('------------------') + print('Total: %d/%d' % (self.points.totalCount(), sum(self.maxes.values()))) + if bonusPic and self.points.totalCount() == 25: + print(""" + + ALL HAIL GRANDPAC. + LONG LIVE THE GHOSTBUSTING KING. + + --- ---- --- + | \ / + \ / | + | + \--/ \--/ + | + | + + | + | + + + | + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + V \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@ + \ / @@@@@@@@@@@@@@@@@@@@@@@@@@ + V @@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@ + /\ @@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@ + /\ / @@@@@@@@@@@@@@@@@@@@@@@@@@@ + / \ @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + / @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@@@@@@@@@ + @@@@@@@@@@@@@@@@@@ + +""") + print(""" +Your grades are NOT yet registered. To register your grades, make sure +to follow your instructor's guidelines to receive credit on your project. +""") + + if self.edxOutput: + self.produceOutput() + if self.gsOutput: + self.produceGradeScopeOutput() + + def addExceptionMessage(self, q, inst, traceback): + """ + Method to format the exception message, this is more complicated because + we need to cgi.escape the traceback but wrap the exception in a
 tag
+    """
+    self.fail('FAIL: Exception raised: %s' % inst)
+    self.addMessage('')
+    for line in traceback.format_exc().split('\n'):
+        self.addMessage(line)
+
+  def addErrorHints(self, exceptionMap, errorInstance, questionNum):
+    typeOf = str(type(errorInstance))
+    questionName = 'q' + questionNum
+    errorHint = ''
+
+    # question specific error hints
+    if exceptionMap.get(questionName):
+      questionMap = exceptionMap.get(questionName)
+      if (questionMap.get(typeOf)):
+        errorHint = questionMap.get(typeOf)
+    # fall back to general error messages if a question specific
+    # one does not exist
+    if (exceptionMap.get(typeOf)):
+      errorHint = exceptionMap.get(typeOf)
+
+    # dont include the HTML if we have no error hint
+    if not errorHint:
+      return ''
+
+    for line in errorHint.split('\n'):
+      self.addMessage(line)
+
+  def produceGradeScopeOutput(self):
+    out_dct = {}
+
+    # total of entire submission
+    total_possible = sum(self.maxes.values())
+    total_score = sum(self.points.values())
+    out_dct['score'] = total_score
+    out_dct['max_score'] = total_possible
+    out_dct['output'] = "Total score (%d / %d)" % (total_score, total_possible)
+
+    # individual tests
+    tests_out = []
+    for name in self.questions:
+      test_out = {}
+      # test name
+      test_out['name'] = name
+      # test score
+      test_out['score'] = self.points[name]
+      test_out['max_score'] = self.maxes[name]
+      # others
+      is_correct = self.points[name] >= self.maxes[name]
+      test_out['output'] = "  Question {num} ({points}/{max}) {correct}".format(
+          num=(name[1] if len(name) == 2 else name),
+          points=test_out['score'],
+          max=test_out['max_score'],
+          correct=('X' if not is_correct else ''),
+      )
+      test_out['tags'] = []
+      tests_out.append(test_out)
+    out_dct['tests'] = tests_out
+
+    # file output
+    with open('gradescope_response.json', 'w') as outfile:
+        json.dump(out_dct, outfile)
+    return
+
+  def produceOutput(self):
+    edxOutput = open('edx_response.html', 'w')
+    edxOutput.write("
") + + # first sum + total_possible = sum(self.maxes.values()) + total_score = sum(self.points.values()) + checkOrX = '' + if (total_score >= total_possible): + checkOrX = '' + header = """ +

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

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