{"id":2207,"date":"2019-06-01T13:00:06","date_gmt":"2019-06-01T12:00:06","guid":{"rendered":"https:\/\/chewett.co.uk\/blog\/?p=2207"},"modified":"2019-06-10T21:44:05","modified_gmt":"2019-06-10T20:44:05","slug":"distributed-automated-halite-3-bot-tester","status":"publish","type":"post","link":"https:\/\/chewett.co.uk\/blog\/2207\/distributed-automated-halite-3-bot-tester\/","title":{"rendered":"Distributed Automated Halite 3 Bot Tester"},"content":{"rendered":"\n<figure class=\"wp-block-image\"><img data-recalc-dims=\"1\" loading=\"lazy\" decoding=\"async\" width=\"678\" height=\"254\" data-attachment-id=\"2218\" data-permalink=\"https:\/\/chewett.co.uk\/blog\/2207\/distributed-automated-halite-3-bot-tester\/halite_distributed_gym\/\" data-orig-file=\"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?fit=800%2C300&amp;ssl=1\" data-orig-size=\"800,300\" data-comments-opened=\"1\" data-image-meta=\"{&quot;aperture&quot;:&quot;0&quot;,&quot;credit&quot;:&quot;&quot;,&quot;camera&quot;:&quot;&quot;,&quot;caption&quot;:&quot;&quot;,&quot;created_timestamp&quot;:&quot;0&quot;,&quot;copyright&quot;:&quot;&quot;,&quot;focal_length&quot;:&quot;0&quot;,&quot;iso&quot;:&quot;0&quot;,&quot;shutter_speed&quot;:&quot;0&quot;,&quot;title&quot;:&quot;&quot;,&quot;orientation&quot;:&quot;0&quot;}\" data-image-title=\"Halite_distributed_gym\" data-image-description=\"\" data-image-caption=\"\" data-medium-file=\"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?fit=300%2C113&amp;ssl=1\" data-large-file=\"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?fit=678%2C254&amp;ssl=1\" src=\"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?resize=678%2C254&#038;ssl=1\" alt=\"\" class=\"wp-image-2218\" srcset=\"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?w=800&amp;ssl=1 800w, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?resize=300%2C113&amp;ssl=1 300w, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?resize=768%2C288&amp;ssl=1 768w, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/Halite_distributed_gym.jpg?resize=50%2C19&amp;ssl=1 50w\" sizes=\"auto, (max-width: 678px) 100vw, 678px\" \/><\/figure>\n\n\n\n<p>In this post I talk about the work I did to improve the Halite 3 Bot testing gym to speed up iterations and compare various versions of my bot.<\/p>\n\n\n\n<!--more-->\n\n\n\n<h2 class=\"wp-block-heading\">Why I spent time Improving the Halite Gym<\/h2>\n\n\n\n<p>During the competition of Halite 3 one piece of provided code was a simple gym system. This python script allowed you to enter various bots and it would pit them against each other.<\/p>\n\n\n\n<p>Depending on the results it would then use the same ranking system as it used during the competition to rank the bots.<\/p>\n\n\n\n<p>This was designed so that you could test various versions of your bot against each other in an automated way. You could specify a number of matches to run and after each match the ranking would be recalculated.<\/p>\n\n\n\n<p>As a automation engineer this system spiked my interest so I could automatically compare my bots against multiple different versions. <\/p>\n\n\n\n<p>After reviewing the system I had a few objectives that I wanted to complete.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>Automate running matches continually<\/li><li>Allow matches to run over multiple computers on multiple operating systems<\/li><li>Improving the bot selection for matches<\/li><li>Storing of interesting replays<\/li><\/ul>\n\n\n\n<p>Below are the details of what I accomplished during the competition.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Converting the database to MySQL<\/h2>\n\n\n\n<p>Originally the gym used a simple Sqlite database to hold the bot information. This works well for a small portable system but I was planning to use it much more rigorously.<\/p>\n\n\n\n<p>The first job I did with changing the gym was to convert it over to use a MySQL database configured on my PC. This was relatively easy as the SQL used previously was relatively independent from the Sqlite database.<\/p>\n\n\n\n<p>This then gave me the advantage of making it easy to connect to it from different computers and have some speed bonuses. This laid the way for some larger changes described later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Improving Bot selection<\/h2>\n\n\n\n<p>Originally the bot selection worked by picking two or four random bots to run.<\/p>\n\n\n\n<p>For a standard tournament this works fine, but when I was iterating my bots I wanted the newer ones to run more often.<\/p>\n\n\n\n<p>After trying various weighting systems I changed the selection code so that it would always pick the bot that had performed in the least number of battles.<\/p>\n\n\n\n<p>Once that bot had been selected, the other one or three bots would be picked randomly. This meant that over the lifetime of the gym the bot with the lowest matches would be run more times, forcing it to compete and be judged against the others.<\/p>\n\n\n\n<p>The random selection of the other bots meant that it was not always playing against the newer bots.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Multi-threading the Battles<\/h2>\n\n\n\n<p>Running the battles is purely limited by compute power and to me this is an easy problem to scale over computers. This fits into the category of &#8220;Embarrassingly\u00a0parallel&#8221; problems. There were some tweaks I needed to perform however.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Stopping race conditions with ranking the bots<\/h3>\n\n\n\n<p>Looking over the gym code the only thing that was stopping me running more than one battle at a time was the ranking calculation. If each ranking was not updated atomically there could be race conditions causing the ranking to become false.<\/p>\n\n\n\n<p>To do this I decided on using a MySQL feature called &#8220;Named locks&#8221;. This allows you to obtain a lock with a specific name from the MySQL server. Until you release the lock or your connection is closed the lock will be retained.<\/p>\n\n\n\n<p>Any other thread will be able to check for the lock being used, and wait until it is released. Before any rank is calculated the thread would get the rank calculation lock.<\/p>\n\n\n\n<p>Once obtained it would perform the rank calculation maths and update the database. Then once it had been completed it would release the lock and allow another thread to update the database.<\/p>\n\n\n\n<p>This allowed me to run multiple copies of the gym on the same PC. This sped up checking the status of a new bot because I was able to more quickly fight it against previous bots.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Running it on multiple computers (and operating systems)<\/h3>\n\n\n\n<p>Since the battles are primarily compute bound I eventually hit a limit of how many threads the PC could successfully run. Adding more slowed down the matches generally and caused the bots to begin timing out.<\/p>\n\n\n\n<p>Since the race condition with updating the ranking has already been dealt with the only problem was how the original gym stored bot details.<\/p>\n\n\n\n<p>Originally when adding a bot you provided a command line to run the bot. For python bots this is something along the line of <code>python MyBot.py<\/code> to run it.<\/p>\n\n\n\n<p>However since now these commands are run on multiple systems this began to have issues. In this instance while <code>python<\/code> evaluated to Python 3.5 on my windows system, when run under Ubuntu this was actually Python 2.7.<\/p>\n\n\n\n<p>Since I was using specific Python 3 commands this then caused problems and the bots failed to run. To resolve this I extended the bot tables to store a path to the script and a runtime.<\/p>\n\n\n\n<p>The new runtime table included a list of runtimes such as.<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>jarfile<\/li><li>python2<\/li><li>python3<\/li><li>php7<\/li><\/ul>\n\n\n\n<p>In the database each bot would specify the path to the script and the runtime needed.<\/p>\n\n\n\n<p>Each host would then have a configuration file which mapped the runtimes to specific paths. When the bot details were passed to the machine to run it would then append the runtime binary with the machine specific path.<\/p>\n\n\n\n<p>With these changes I was able to run the bot ranking code across multiple computers to very rapidly iterate bots.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Storing Interesting replays<\/h2>\n\n\n\n<p>I never manged to store interesting replays however the plan was to have a metric to decide what is interesting.<\/p>\n\n\n\n<p>This was going to be based on a whether one of the following conditions occurred:<\/p>\n\n\n\n<ul class=\"wp-block-list\"><li>If any bot crashed &#8211; This suggests a logic error<\/li><li>If any bot timed out &#8211; This suggests a bot needs handling to keep track of timings better (as bots are allowed limited time to run)<\/li><li>If any bot lost\/won to a much weaker\/stronger bot (as by the ranking)  &#8211; As this may indicate a weakness to a specific strategy<\/li><li>If any bot collected a large amount, or very small amount of halite &#8211; As this may indicate a weakness or strength with the bot and the given map<\/li><\/ul>\n\n\n\n<p>During the competition I manually reviewed anything that looked odd to check the state of the automated matches.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Using the Automated Halite Tester during the Competition<\/h2>\n\n\n\n<p>During the competition I quickly found that I could run many thousands of matches in between the development of each new bot. This meant that I was able to quickly compare the version of each bot.<\/p>\n\n\n\n<p>Overnight since I was able to dedicate multiple machines to the tester I could run millions of matches. To utilise this power I created bots which could be configured with various parameters.<\/p>\n\n\n\n<p>Each night I would submit multiple versions of each bot with the parameters tuned. By running many matches across these bots I was able to determine optimal parameters. An an example one area that was tuned was the amount of halite that would be collected on a cell before moving on.<\/p>\n\n\n\n<p>One concern with this was finding local maximum&#8217;s, or finding the best bot which could compete against my own bots. To reduce this I wrote a number of bots in a number of different languages and in different ways.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Summary<\/h2>\n\n\n\n<p>Overall I enjoyed the competition and the challenges of automating it. By utilising my automation expertise I was able to create a system which could evaluate my bots. This  had advantages as I didn&#8217;t need to submit them to the competition and could run many matches.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>In this post I talk about the work I did to improve the Halite 3 Bot testing gym to speed up iterations and compare various versions of my bot.<\/p>\n","protected":false},"author":1,"featured_media":2219,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_exactmetrics_skip_tracking":false,"_exactmetrics_sitenote_active":false,"_exactmetrics_sitenote_note":"","_exactmetrics_sitenote_category":0,"jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[98],"tags":[102,312,347,66,184],"class_list":["post-2207","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-software","tag-distributed-computing","tag-halite","tag-halite-3","tag-php","tag-python"],"wppr_data":{"cwp_meta_box_check":"No"},"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/06\/halite_distributed_gym.jpg?fit=654%2C653&ssl=1","jetpack_shortlink":"https:\/\/wp.me\/p2toWX-zB","jetpack_sharing_enabled":true,"jetpack-related-posts":[{"id":2120,"url":"https:\/\/chewett.co.uk\/blog\/2120\/halite-ai-challenge-2018-results\/","url_meta":{"origin":2207,"position":0},"title":"Halite AI Challenge 2018 Results","author":"Chewett","date":"April 6, 2019","format":false,"excerpt":"Today I am writing about my bot in the Halite 3 2018 competition Final Results I talked a little about the Halite 2018 competition in an earlier blogpost. This is an annual open source competitive AI challenge designed around a particular game. This year the objective was to collect \"halite\"\u2026","rel":"","context":"In \"AI\"","block_context":{"text":"AI","link":"https:\/\/chewett.co.uk\/blog\/tag\/ai\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/04\/halite_results_post_icon.jpg?fit=654%2C653&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/04\/halite_results_post_icon.jpg?fit=654%2C653&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2019\/04\/halite_results_post_icon.jpg?fit=654%2C653&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":1640,"url":"https:\/\/chewett.co.uk\/blog\/1640\/halite-ai-challenge\/","url_meta":{"origin":2207,"position":1},"title":"Halite AI Challenge","author":"Chewett","date":"October 20, 2018","format":false,"excerpt":"This blog post talks about the Halite AI Challenge that I and many of my colleagues are currently competing in. Halite AI Challenge The Halite AI challenge is an annual\u00a0open-source artificial intelligence challenge, created by the company Two Sigma. The challenge each year focusses around building a bot which will\u2026","rel":"","context":"In &quot;Informational&quot;","block_context":{"text":"Informational","link":"https:\/\/chewett.co.uk\/blog\/category\/informational\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2018\/10\/post_icon.jpg?fit=654%2C653&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2018\/10\/post_icon.jpg?fit=654%2C653&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2018\/10\/post_icon.jpg?fit=654%2C653&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":2680,"url":"https:\/\/chewett.co.uk\/blog\/2680\/raspberry-pi-cluster-node-16-python-3-codebase-refactor\/","url_meta":{"origin":2207,"position":2},"title":"Raspberry Pi Cluster Node \u2013 16 Python 3 Codebase Refactor","author":"Chewett","date":"October 24, 2020","format":false,"excerpt":"This post builds on\u00a0my previous posts in the Raspberry Pi Cluster series\u00a0by improving the codebase for Python 3. Moving to Python 3 Python 2 was marked end of life on January 1st, 2020 and therefore applications should ideally be no longer using Python 2. There will still be a lot\u2026","rel":"","context":"In &quot;Raspberry Pi Cluster&quot;","block_context":{"text":"Raspberry Pi Cluster","link":"https:\/\/chewett.co.uk\/blog\/category\/raspberry-pi-cluster\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2020\/10\/raspi_cluster_16_python3refactor_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2020\/10\/raspi_cluster_16_python3refactor_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2020\/10\/raspi_cluster_16_python3refactor_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2020\/10\/raspi_cluster_16_python3refactor_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2020\/10\/raspi_cluster_16_python3refactor_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":343,"url":"https:\/\/chewett.co.uk\/blog\/343\/raspberrypivcgencmd-python-library-access-raspberry-pi-vcgencmd-command\/","url_meta":{"origin":2207,"position":3},"title":"RaspberryPiVcgencmd A python library to access Raspberry Pi vcgencmd command","author":"Chewett","date":"May 31, 2017","format":false,"excerpt":"To access some of the lower level aspects of the Raspberry Pi's config you can use the built in command vcgencmd. This is a useful on the command line but to easily access it in a program you need to parse the output. I am planning to use this command\u2026","rel":"","context":"In &quot;Raspberry Pi Cluster&quot;","block_context":{"text":"Raspberry Pi Cluster","link":"https:\/\/chewett.co.uk\/blog\/category\/raspberry-pi-cluster\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/05\/vcgencmd.png?fit=628%2C288&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/05\/vcgencmd.png?fit=628%2C288&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/05\/vcgencmd.png?fit=628%2C288&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":741,"url":"https:\/\/chewett.co.uk\/blog\/741\/raspberry-pi-cluster-node-01-logging-liveness\/","url_meta":{"origin":2207,"position":4},"title":"Raspberry Pi Cluster Node &#8211; 01 Logging Liveness","author":"Chewett","date":"November 1, 2017","format":false,"excerpt":"This post describes how to make a simple python script that logs the node is alive every 10 seconds. Why we are going to log each node is alive As discussed in the previous post on Distributed Computing on the Raspberry Pi Cluster there will be many slaves and a\u2026","rel":"","context":"In &quot;Raspberry Pi Cluster&quot;","block_context":{"text":"Raspberry Pi Cluster","link":"https:\/\/chewett.co.uk\/blog\/category\/raspberry-pi-cluster\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/10\/rpi_cluster_01_logging_liveness.jpg?fit=800%2C800&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/10\/rpi_cluster_01_logging_liveness.jpg?fit=800%2C800&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/10\/rpi_cluster_01_logging_liveness.jpg?fit=800%2C800&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2017\/10\/rpi_cluster_01_logging_liveness.jpg?fit=800%2C800&ssl=1&resize=700%2C400 2x"},"classes":[]},{"id":2780,"url":"https:\/\/chewett.co.uk\/blog\/2780\/raspberry-pi-cluster-node-18-raspberry-pi-temperature-monitoring\/","url_meta":{"origin":2207,"position":5},"title":"Raspberry Pi Cluster Node \u2013 18 Raspberry Pi Temperature Monitoring","author":"Chewett","date":"February 20, 2021","format":false,"excerpt":"This post builds on\u00a0my previous posts in the Raspberry Pi Cluster series\u00a0by starting to log temperature with the RaspberryPiVcgencmd Python module. Installing RaspberryPiVcgencmd RaspberryPiVcgencmd is a small python module aimed to control vcgencmd and allow programmatic access to it. This can be installed with the following command. python3 -m pip\u2026","rel":"","context":"In &quot;Raspberry Pi Cluster&quot;","block_context":{"text":"Raspberry Pi Cluster","link":"https:\/\/chewett.co.uk\/blog\/category\/raspberry-pi-cluster\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2021\/02\/raspi_cluster_18_cputemperature_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2021\/02\/raspi_cluster_18_cputemperature_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2021\/02\/raspi_cluster_18_cputemperature_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2021\/02\/raspi_cluster_18_cputemperature_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/chewett.co.uk\/blog\/wp-content\/uploads\/2021\/02\/raspi_cluster_18_cputemperature_posticon_OUTPUT.png?fit=1200%2C628&ssl=1&resize=1050%2C600 3x"},"classes":[]}],"_links":{"self":[{"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/posts\/2207","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/comments?post=2207"}],"version-history":[{"count":4,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/posts\/2207\/revisions"}],"predecessor-version":[{"id":2220,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/posts\/2207\/revisions\/2220"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/media\/2219"}],"wp:attachment":[{"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/media?parent=2207"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/categories?post=2207"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/chewett.co.uk\/blog\/wp-json\/wp\/v2\/tags?post=2207"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}