<?xml version='1.0' encoding='UTF-8'?><?xml-stylesheet href="http://www.blogger.com/styles/atom.css" type="text/css"?><feed xmlns='http://www.w3.org/2005/Atom' xmlns:openSearch='http://a9.com/-/spec/opensearchrss/1.0/' xmlns:blogger='http://schemas.google.com/blogger/2008' xmlns:georss='http://www.georss.org/georss' xmlns:gd="http://schemas.google.com/g/2005" xmlns:thr='http://purl.org/syndication/thread/1.0'><id>tag:blogger.com,1999:blog-5196359578514433536</id><updated>2026-03-08T05:40:15.415-07:00</updated><category term="death has a million stomping boots"/><category term="captain disaster"/><category term="sale"/><category term="reviews"/><category term="the dark side of the moon"/><category term="feedback"/><category term="demo"/><category term="ebook"/><category term="download stats"/><category term="update"/><category term="screenshots"/><category term="beta testing"/><category term="ratings"/><category term="playable demo"/><category term="voice acting"/><category term="game development"/><category term="beta screenshots"/><category term="bundle"/><category term="media coverage"/><category term="concept art"/><category term="goldpieces"/><category term="new release"/><category term="steam"/><category term="AI"/><category term="damaris touch"/><category term="gameplay trailer"/><category term="pre-alpha screenshots"/><category term="teaser trailer"/><category term="there&#39;s a hole in my galaxy"/><category term="ags awards"/><category term="gamejolt"/><category term="release date"/><category term="scabb"/><category term="special offer"/><category term="walkthrough"/><category term="free ebook"/><category term="freeware game"/><category term="javascript"/><category term="stats"/><category term="two worlds of riskara"/><category term="video"/><category term="audiobooks"/><category term="chat gpt"/><category term="cryptocurrency"/><category term="fireflower games"/><category term="giveaway"/><category term="music"/><category term="puzzle game"/><category term="adventurex"/><category term="alpha screenshots"/><category term="animation"/><category term="awards"/><category term="download sites"/><category term="flow problem"/><category term="interview"/><category term="pre-order"/><category term="progress report"/><category term="retro"/><category term="sales figures"/><category term="the ancient art of staying alive"/><category term="the games"/><category term="the stories"/><category term="video review"/><category term="adventure game"/><category term="ags"/><category term="ai hallucinations"/><category term="announcements"/><category term="before and after"/><category term="captain disaster collection"/><category term="design notes"/><category term="developer notes"/><category term="disaster area"/><category term="experiment"/><category term="flash sale"/><category term="free online game"/><category term="fundraising"/><category term="gameplay video"/><category term="hints"/><category term="kindle"/><category term="logo"/><category term="milestone"/><category term="online quiz"/><category term="patreon"/><category term="physics"/><category term="poll"/><category term="quiz"/><category term="snow problem"/><category term="social networking"/><category term="team disaster"/><category term="the trouble with screeching sapper serpents"/><category term="trailer"/><category term="version update"/><category term="act i"/><category term="act ii"/><category term="act iii"/><category term="aggie awards"/><category term="ags bake sale"/><category term="ai search"/><category term="amazon"/><category term="arcade"/><category term="artificial intelligance"/><category term="audio"/><category term="banner."/><category term="bertram fiddle"/><category term="bmt micro"/><category term="character design"/><category term="colourblind mode"/><category term="colourdoku"/><category term="credits"/><category term="css"/><category term="demo release date"/><category term="demo release date. playtesting"/><category term="design docs"/><category term="dev folder"/><category term="discount"/><category term="ebook trailer"/><category term="episode 1"/><category term="fake adverts"/><category term="following freeware"/><category term="gamedev"/><category term="hair styling"/><category term="html"/><category term="in production"/><category term="itch"/><category term="itch summer sale"/><category term="launch trailer"/><category term="lets play"/><category term="lifetime achievement award"/><category term="linux"/><category term="manual"/><category term="marketplace"/><category term="milestones"/><category term="minimalist"/><category term="missing word"/><category term="new game in development"/><category term="noughts and crosses"/><category term="novelisation"/><category term="official website"/><category term="player character"/><category term="point and click"/><category term="puzzle dependencies"/><category term="puzzles"/><category term="readers choice"/><category term="retro games"/><category term="room layouts"/><category term="sales"/><category term="sample chapter"/><category term="secure password generator"/><category term="short stories"/><category term="sketches"/><category term="snake. agsnake"/><category term="sound"/><category term="sponsorship"/><category term="sprites"/><category term="star wars day"/><category term="status build"/><category term="tahimg"/><category term="the last bite"/><category term="ubuntu"/><category term="updated demo"/><category term="water physics"/><category term="wine"/><title type='text'>Captain Disaster - Official Website</title><subtitle type='html'>All about the games and ebooks starring the disastrous captain!</subtitle><link rel='http://schemas.google.com/g/2005#feed' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/posts/default'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default?redirect=false'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/'/><link rel='hub' href='http://pubsubhubbub.appspot.com/'/><link rel='next' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default?start-index=26&amp;max-results=25&amp;redirect=false'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><generator version='7.00' uri='http://www.blogger.com'>Blogger</generator><openSearch:totalResults>425</openSearch:totalResults><openSearch:startIndex>1</openSearch:startIndex><openSearch:itemsPerPage>25</openSearch:itemsPerPage><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-3779607235397587986</id><published>2025-11-06T11:05:00.000-08:00</published><updated>2025-11-06T11:05:34.610-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="developer notes"/><category scheme="http://www.blogger.com/atom/ns#" term="game development"/><category scheme="http://www.blogger.com/atom/ns#" term="two worlds of riskara"/><title type='text'>Update on Captain Disaster and the Two Worlds of Riskara</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;This is a bit hard to write but I guess most people will have thought it&#39;s probable by now - I won&#39;t be shipping the game in 2025. It&#39;s so close to completion in a lot of ways but I&#39;ve had difficulty getting the last few things done ready for a final test phase. There are numerous reasons behind this but suffice to say that creating a game of this scale in your spare time is a difficult proposition at the best of times - and certain circumstances over the past year meant I didn&#39;t have the best circumstances to be able to work on the game in the way I needed to. (Not looking for sympathy or about to go into details - it&#39;s just life, and like sentient pizzas wearing nose-glasses, it happens.)&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2242270/ss_15ec0721e988b091bff143271df0b7259b237572.1920x1080.jpg?t=1752681144&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;720&quot; data-original-width=&quot;1280&quot; height=&quot;360&quot; src=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2242270/ss_15ec0721e988b091bff143271df0b7259b237572.1920x1080.jpg?t=1752681144&quot; width=&quot;640&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://img.itch.zone/aW1nLzIzNzg2MTQ5LnBuZw==/315x250%23c/Of7L2U.png&quot; imageanchor=&quot;1&quot; style=&quot;clear: right; float: right; margin-bottom: 1em; margin-left: 1em;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;250&quot; data-original-width=&quot;315&quot; height=&quot;159&quot; src=&quot;https://img.itch.zone/aW1nLzIzNzg2MTQ5LnBuZw==/315x250%23c/Of7L2U.png&quot; width=&quot;200&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Part of the problem has been struggling for motivation, which is again down to a few different factors - my recent small project (&lt;b&gt;&lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/10/new-game-released-agsnake-last-bite.html&quot;&gt;AGSnake: The Last Bite&lt;/a&gt;&lt;/b&gt;) was an attempt to get myself back into it. To some extent it&#39;s worked (and it was a project that had been in the back of my mind for some time - I&#39;m pretty pleased with how it turned out)... I just need to find that extra 10% to push myself back into the zone. Well, maybe more like 20%. At most, 30%... but I digress...&lt;br /&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;On more positive news, I&#39;ve had some great voicing completed in recent months to really bring the characters to life. The voice cast now includes Sally Beaumont, Katie Aitken, and AGS Forum alumni Durinde, SinSin, Cat, Mandle, Viking, TheBitPriest, ManicMatt - the biggest problem I have with the voicing now is that I often don&#39;t like my own voicing in the game! I may need to redo a lot of my own lines, but it will be worth it to improve the overall quality.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Anyway, my currently plan is to release the game early in 2026, but I&#39;m not setting a definite release date yet (although I do have a particular date in mind) - I only want to release the game when I&#39;m really satisifed that it&#39;s finished and as good as I&#39;m going to be able to make it. In all likelihood, sales are going to be nothing life-changing, so from that point of view I don&#39;t have sales pressure to release it - on the other hand, I don&#39;t like feeling that I&#39;ve let people down because I led people to believe the game would be released this year. But again, life happens - I&#39;m not the first, nor will I be the last, indie dev who has to push back release - in fact two games I&#39;ve backed on Kickstarter have announced that their games will now be out in 2026 (yes, that did give me a little push to make this announcement publicly).&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2242270/ss_d0f4c1f841ec7fe2220ce5847435b58dc565dd7a.1920x1080.jpg?t=1752681144&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1080&quot; data-original-width=&quot;1920&quot; height=&quot;360&quot; src=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2242270/ss_d0f4c1f841ec7fe2220ce5847435b58dc565dd7a.1920x1080.jpg?t=1752681144&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Speaking of other games that are coming out in 2026, check out &lt;b&gt;&lt;a href=&quot;https://store.steampowered.com/app/2797960/Confidential_Killings__A_Detective_Game/&quot; target=&quot;_blank&quot;&gt;Confidential Killings&lt;/a&gt;&lt;/b&gt; by Brane - with graphics by Lorenzo, the artist for Captain Disaster&amp;nbsp;and the Two Worlds of Riskara. It&#39;s an atmospheric murder-mystery - the demo will give you a really good idea of the flavour of the game.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2797960/2748d090c22bc5b9d283b17ee29290c325e718af/ss_2748d090c22bc5b9d283b17ee29290c325e718af.1920x1080.jpg?t=1760444281&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1080&quot; data-original-width=&quot;1920&quot; height=&quot;360&quot; src=&quot;https://shared.akamai.steamstatic.com/store_item_assets/steam/apps/2797960/2748d090c22bc5b9d283b17ee29290c325e718af/ss_2748d090c22bc5b9d283b17ee29290c325e718af.1920x1080.jpg?t=1760444281&quot; width=&quot;640&quot; /&gt;&lt;/span&gt;&lt;/a&gt;&lt;/div&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;span&gt;&lt;br /&gt;&lt;/span&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Well that&#39;s it from me, for now. If you&#39;re looking forward to the game, maybe drop me a comment here letting me know - that could go a long way to helping me focus on the final development stretch!!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/3779607235397587986/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/11/update-on-captain-disaster-and-two.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/3779607235397587986'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/3779607235397587986'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/11/update-on-captain-disaster-and-two.html' title='Update on Captain Disaster and the Two Worlds of Riskara'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-7851717845793832129</id><published>2025-10-26T02:52:00.000-07:00</published><updated>2025-10-26T02:52:19.273-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="freeware game"/><category scheme="http://www.blogger.com/atom/ns#" term="new release"/><category scheme="http://www.blogger.com/atom/ns#" term="snake. agsnake"/><category scheme="http://www.blogger.com/atom/ns#" term="the last bite"/><title type='text'>New game released! - AGSnake: The Last Bite</title><content type='html'>&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;For reasons beyond my understanding, I&#39;ve wanted to make a Snake game in AGS for years. Needing a small side-project to help me focus my brain for the last development stint of Captain Disaster 3, I decided now was the time to make it. I had fun working out the game mechanics, how to track snake music and direction, collisions etc - yes you heard me, I found it FUN! - anyway, I added a few features to hopefully make it more fun, like power ups / downs and 2-player cooperative mode.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&amp;nbsp;

&lt;iframe frameborder=&quot;0&quot; height=&quot;167&quot; src=&quot;https://itch.io/embed/3986673&quot; width=&quot;552&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/agsnake-the-last-bite&quot;&gt;AGSnake: The Last Bite by CaptainD&lt;/a&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Hope you enjoy it!&lt;/span&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-IXisaDgfJyDPI2Z38s6_kD7D8VLglXazTjJcTV7cMZaM7y5B5jwzBNwWSwEPe3Tpj2u-YGEoA2MtXmv47wpElj_gv0537RQqz8En0qvbe0VR_mWuqd_JCC2l6tlEQ9GNgggBQx3y1fHlMtrwRIogYpVR5aZNe1oVNtUM6fbzxU4kZfi8GzmmXF3dkY/s630/Itch%20Cover.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;500&quot; data-original-width=&quot;630&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-IXisaDgfJyDPI2Z38s6_kD7D8VLglXazTjJcTV7cMZaM7y5B5jwzBNwWSwEPe3Tpj2u-YGEoA2MtXmv47wpElj_gv0537RQqz8En0qvbe0VR_mWuqd_JCC2l6tlEQ9GNgggBQx3y1fHlMtrwRIogYpVR5aZNe1oVNtUM6fbzxU4kZfi8GzmmXF3dkY/s16000/Itch%20Cover.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;i&gt;Incidentally, I subtitled it &quot;The Last Bite&quot; because it turns out that another AGS user had the idea of developing a Snake game with the name AGSnake - albeit, 14 years later, that hasn&#39;t been released (but don&#39;t give up hope, you never know!).&lt;br /&gt;&lt;/i&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/7851717845793832129/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/10/new-game-released-agsnake-last-bite.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7851717845793832129'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7851717845793832129'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/10/new-game-released-agsnake-last-bite.html' title='New game released! - AGSnake: The Last Bite'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiV-IXisaDgfJyDPI2Z38s6_kD7D8VLglXazTjJcTV7cMZaM7y5B5jwzBNwWSwEPe3Tpj2u-YGEoA2MtXmv47wpElj_gv0537RQqz8En0qvbe0VR_mWuqd_JCC2l6tlEQ9GNgggBQx3y1fHlMtrwRIogYpVR5aZNe1oVNtUM6fbzxU4kZfi8GzmmXF3dkY/s72-c/Itch%20Cover.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-5476252728750781530</id><published>2025-09-29T02:19:00.000-07:00</published><updated>2025-09-29T02:19:50.990-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="two worlds of riskara"/><category scheme="http://www.blogger.com/atom/ns#" term="voice acting"/><title type='text'>Voice test - Corneliustin</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Corneliustin was a fun character to write - he&#39;s kind of a cross between Stan from Monkey Island and Foghorn Leghorn. When we released the demo which had no voicing, I watched a few playthroughs where the person playing was adding their own voicing - and without exception, everyone voiced him as a Texan, so my writing must have been reasonably successful!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Here you can see - er, hear - Rob McManus with his spot-on interpretation of the character.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;(Incidentally the sparks one the faulty control panel affect my first couple of words. It wasn&#39;t me doing weird things with my voice!!)&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;iframe allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://www.youtube.com/embed/gS0xnSqOCGE?si=jvNM8SuCkef-OJ59&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/5476252728750781530/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/voice-test-corneliustin.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5476252728750781530'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5476252728750781530'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/voice-test-corneliustin.html' title='Voice test - Corneliustin'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://img.youtube.com/vi/gS0xnSqOCGE/default.jpg" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-7063166848723470613</id><published>2025-09-28T04:07:00.000-07:00</published><updated>2025-09-28T04:07:44.876-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="audiobooks"/><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><title type='text'>Captain Disaster Audiobooks</title><content type='html'>&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiBCM1KGjmBFaYYai1qBhnMQrxwJtPkJj05-6uBHqqMhAYGDIp9gPuAWFNzff8WkFBjKRJGKiQdDEOCLkIWzYZFEQkoEcz-CUDAt4iZT2z0VqmP5SNZ4rbVhDM27R1YyZ_baoiLqA6Pr0cj_12bf0mOLmEhoKw5wh4tW3pHPd1gpeRQIEbZLETdmrepp_Y&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;275&quot; data-original-width=&quot;347&quot; height=&quot;240&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiBCM1KGjmBFaYYai1qBhnMQrxwJtPkJj05-6uBHqqMhAYGDIp9gPuAWFNzff8WkFBjKRJGKiQdDEOCLkIWzYZFEQkoEcz-CUDAt4iZT2z0VqmP5SNZ4rbVhDM27R1YyZ_baoiLqA6Pr0cj_12bf0mOLmEhoKw5wh4tW3pHPd1gpeRQIEbZLETdmrepp_Y&quot; width=&quot;303&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;A long time ago, in a galaxy pretty nearby, I started editing my original set of short stories starring the beloved space doofus and recording the results as audiobooks. I did the first five, and now I&#39;m admitting defeat - I just don&#39;t have the time or energy to complete these. With the first 5 already recorded and available, I have made the decision to simply leave things as they are, but reduce the price from $10.99 (originally had 11 episodes to edit / record) to $4.99, to reflect how many episodes are actually available.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Below is an excerpt from Episode One, and the Itch page has excerpts for all 5 episodes.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://captaind.itch.io/the-captain-disaster-collection-audiobook&quot; target=&quot;_blank&quot;&gt;Click here&lt;/a&gt;&lt;/b&gt; for the relevant page on Itch.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;iframe allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; allowfullscreen=&quot;&quot; frameborder=&quot;0&quot; height=&quot;315&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; src=&quot;https://www.youtube.com/embed/TJ1bZAR9_Bw?si=vn2ulrZB-wfsrmGW&quot; title=&quot;YouTube video player&quot; width=&quot;560&quot;&gt;&lt;/iframe&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/7063166848723470613/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/captain-disaster-audiobooks.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7063166848723470613'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7063166848723470613'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/captain-disaster-audiobooks.html' title='Captain Disaster Audiobooks'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEiBCM1KGjmBFaYYai1qBhnMQrxwJtPkJj05-6uBHqqMhAYGDIp9gPuAWFNzff8WkFBjKRJGKiQdDEOCLkIWzYZFEQkoEcz-CUDAt4iZT2z0VqmP5SNZ4rbVhDM27R1YyZ_baoiLqA6Pr0cj_12bf0mOLmEhoKw5wh4tW3pHPd1gpeRQIEbZLETdmrepp_Y=s72-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-331503827824863741</id><published>2025-09-04T13:15:00.000-07:00</published><updated>2025-09-04T13:15:02.097-07:00</updated><title type='text'>Website milestone</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;In the big scheme of things it means nothing of course, but it feels quite significant to me - this website has now gone past 750,000 page views. Or, to put it in a way that perhaps sounds more impressive, three quarters of a million views.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;That&#39;s all - it means nothing really, but I do love a good milestone! :-)&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/331503827824863741/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/website-milestone.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/331503827824863741'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/331503827824863741'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/09/website-milestone.html' title='Website milestone'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-6003018053984018792</id><published>2025-08-24T02:29:00.000-07:00</published><updated>2025-08-24T02:29:00.662-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="death has a million stomping boots"/><category scheme="http://www.blogger.com/atom/ns#" term="flash sale"/><title type='text'>Flash Sale (Captain Disaster in: Death Has A Million Stomping Boots)</title><content type='html'>&lt;p&gt;60% off at Itch for 3 days:&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;iframe frameborder=&quot;0&quot; height=&quot;167&quot; src=&quot;https://itch.io/embed/203859&quot; width=&quot;552&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/captain-disaster-in-death-has-a-million-stomping-boots&quot;&gt;Captain Disaster in: Death Has A Million Stomping Boots by CaptainD&lt;/a&gt;&lt;/iframe&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEitwp0XVrVmhDCfz_h4JsVLBBrnUdf_LjDCqRFZKbcIRWcLqjQVTqLTCwdaN8zQR9elWW0XQV4VvU-fTDtoKZFUEU3edXhqu_qoIATePexNwmdYHu6qPjAc4rGA4-kZBwcC-zGb5P3PrjDFZKVun2L-x0IqBoDrsLP6PxVmyi2Fc8YWhNIS10NMSKQTSDY&quot; style=&quot;clear: left; float: left; margin-bottom: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;675&quot; data-original-width=&quot;1253&quot; height=&quot;172&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEitwp0XVrVmhDCfz_h4JsVLBBrnUdf_LjDCqRFZKbcIRWcLqjQVTqLTCwdaN8zQR9elWW0XQV4VvU-fTDtoKZFUEU3edXhqu_qoIATePexNwmdYHu6qPjAc4rGA4-kZBwcC-zGb5P3PrjDFZKVun2L-x0IqBoDrsLP6PxVmyi2Fc8YWhNIS10NMSKQTSDY=w320-h172&quot; width=&quot;320&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/6003018053984018792/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/flash-sale-captain-disaster-in-death.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6003018053984018792'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6003018053984018792'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/flash-sale-captain-disaster-in-death.html' title='Flash Sale (Captain Disaster in: Death Has A Million Stomping Boots)'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEitwp0XVrVmhDCfz_h4JsVLBBrnUdf_LjDCqRFZKbcIRWcLqjQVTqLTCwdaN8zQR9elWW0XQV4VvU-fTDtoKZFUEU3edXhqu_qoIATePexNwmdYHu6qPjAc4rGA4-kZBwcC-zGb5P3PrjDFZKVun2L-x0IqBoDrsLP6PxVmyi2Fc8YWhNIS10NMSKQTSDY=s72-w320-h172-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-4734929380378680248</id><published>2025-08-14T10:20:00.000-07:00</published><updated>2025-08-14T10:20:24.072-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="bundle"/><category scheme="http://www.blogger.com/atom/ns#" term="puzzle game"/><category scheme="http://www.blogger.com/atom/ns#" term="retro"/><category scheme="http://www.blogger.com/atom/ns#" term="retro games"/><category scheme="http://www.blogger.com/atom/ns#" term="sale"/><category scheme="http://www.blogger.com/atom/ns#" term="snow problem"/><category scheme="http://www.blogger.com/atom/ns#" term="special offer"/><category scheme="http://www.blogger.com/atom/ns#" term="the ancient art of staying alive"/><title type='text'>&quot;So Retro&quot; bundle offer</title><content type='html'>&lt;p&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Until the end of August 2025, my two very, very retro games on Itch are available for just $1 each, or $1.50 for both!&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Can you stay alive against the evil Crimson Tide Consortium in &lt;b&gt;&lt;a href=&quot;https://captaind.itch.io/the-ancient-art-of-staying-alive&quot; target=&quot;_blank&quot;&gt;The Ancient Art of Staying Alive&lt;/a&gt;&lt;/b&gt;?&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEho0MRuRW7Cc_ZsRcWqlsgafcaONIa06nCkuLVH4cPRFVLKDcXoKjxoFxzXb53KZ5Ct0uEGl_5oZi8_pFnQbBIxrwx1WkgOLHjozOQLNaFaSFHqNlIhgEQy1QgVhAqj6Cca-6X8oezD5UxgTMqdnQ0H4-1_wekf9yOAHaAS4WgYRXiXueJfJlLq-h7nbVc&quot; style=&quot;margin-left: 1em; margin-right: 1em; text-align: center;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;250&quot; data-original-width=&quot;315&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEho0MRuRW7Cc_ZsRcWqlsgafcaONIa06nCkuLVH4cPRFVLKDcXoKjxoFxzXb53KZ5Ct0uEGl_5oZi8_pFnQbBIxrwx1WkgOLHjozOQLNaFaSFHqNlIhgEQy1QgVhAqj6Cca-6X8oezD5UxgTMqdnQ0H4-1_wekf9yOAHaAS4WgYRXiXueJfJlLq-h7nbVc=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;Can you guide enough snowflakes to their destination in the fiendish puzzle game &lt;/span&gt;&lt;b style=&quot;font-size: x-large;&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/snow-problem&quot; target=&quot;_blank&quot;&gt;Snow Problem&lt;/a&gt;&lt;/b&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;?&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg8zvgIe2Bgr-KjCAU6VW37JRw8ieR_WLTZbvrbIMv5Xqt-8-69anaTB2lOZK_R9sCVxosTicrFik-1NxkeWhSeVTYSdjuNB_fYgiWcsVLzNL069SMKXq4peGiu6gRC7YB8YiPvorG-UjuhiGVn6zaU4w-d7Q9zhoOHuDsGhyXc8zrsgvyHo4WdjTAXtXs&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;250&quot; data-original-width=&quot;315&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEg8zvgIe2Bgr-KjCAU6VW37JRw8ieR_WLTZbvrbIMv5Xqt-8-69anaTB2lOZK_R9sCVxosTicrFik-1NxkeWhSeVTYSdjuNB_fYgiWcsVLzNL069SMKXq4peGiu6gRC7YB8YiPvorG-UjuhiGVn6zaU4w-d7Q9zhoOHuDsGhyXc8zrsgvyHo4WdjTAXtXs=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;Or even... get both?!!?!? Yes, &lt;a href=&quot;https://itch.io/s/158582/so-retro&quot; target=&quot;_blank&quot;&gt;&lt;b&gt;two full retro game for less than the price of Horse Armour&lt;/b&gt;&lt;/a&gt;!&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/4734929380378680248/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/so-retro-bundle-offer.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4734929380378680248'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4734929380378680248'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/so-retro-bundle-offer.html' title='&quot;So Retro&quot; bundle offer'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEho0MRuRW7Cc_ZsRcWqlsgafcaONIa06nCkuLVH4cPRFVLKDcXoKjxoFxzXb53KZ5Ct0uEGl_5oZi8_pFnQbBIxrwx1WkgOLHjozOQLNaFaSFHqNlIhgEQy1QgVhAqj6Cca-6X8oezD5UxgTMqdnQ0H4-1_wekf9yOAHaAS4WgYRXiXueJfJlLq-h7nbVc=s72-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-5237937333452004527</id><published>2025-08-11T08:48:00.000-07:00</published><updated>2025-08-11T08:48:35.480-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="audio"/><category scheme="http://www.blogger.com/atom/ns#" term="two worlds of riskara"/><category scheme="http://www.blogger.com/atom/ns#" term="voice acting"/><title type='text'>Riskaran Voices </title><content type='html'>&lt;p&gt;&lt;span style=&quot;font-size: large; text-align: justify;&quot;&gt;I wish I had a really exciting announcement or update to give you, but really at this stage all I can say is that slowly, steadily, but more slowly than I&#39;d really like, the voice lines for the game are getting recorded, edited and checked.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;So... yeah, that&#39;s it really. Work progressed, wishlists (hopefully!) increase, and we edge closer to a release candidate. It&#39;s true that I&#39;ve had a few delays with both my own voicing and some cast members, but I think it&#39;s fair to say that we&#39;re well past the halfway mark now.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Soon I&#39;ll post a video or two with multiple characters speaking so you can get a real feel for how the final game is going to be, but in the meantime here&#39;s a screen grab of a voice line cut out and ready to go into the game...&lt;/span&gt;&lt;/p&gt;&lt;div&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmmVYt_d4g5UG28F5tMax8-X-6a-a0iYQxKRnrtYXT6nImmafD27_SQkWWXr_0d6P0NBnw0OZG0vLcmW_RjIdcFZnF630TYdlNBv9d5cLVP9CQKw1gmLIOe9FvR7qfR8XA6kWMmz2fDiHry2WT2_5unkHu-OAhqIg66y3UEQK_kxIeJ3UYulalcZOkdZY/s816/Audio%20editing.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;816&quot; data-original-width=&quot;638&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmmVYt_d4g5UG28F5tMax8-X-6a-a0iYQxKRnrtYXT6nImmafD27_SQkWWXr_0d6P0NBnw0OZG0vLcmW_RjIdcFZnF630TYdlNBv9d5cLVP9CQKw1gmLIOe9FvR7qfR8XA6kWMmz2fDiHry2WT2_5unkHu-OAhqIg66y3UEQK_kxIeJ3UYulalcZOkdZY/s16000/Audio%20editing.png&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/5237937333452004527/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/riskaran-voices.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5237937333452004527'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5237937333452004527'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/08/riskaran-voices.html' title='Riskaran Voices '/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhmmVYt_d4g5UG28F5tMax8-X-6a-a0iYQxKRnrtYXT6nImmafD27_SQkWWXr_0d6P0NBnw0OZG0vLcmW_RjIdcFZnF630TYdlNBv9d5cLVP9CQKw1gmLIOe9FvR7qfR8XA6kWMmz2fDiHry2WT2_5unkHu-OAhqIg66y3UEQK_kxIeJ3UYulalcZOkdZY/s72-c/Audio%20editing.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-6707418149807835983</id><published>2025-07-29T02:14:00.000-07:00</published><updated>2025-08-06T03:14:39.948-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="adventure game"/><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="ai hallucinations"/><category scheme="http://www.blogger.com/atom/ns#" term="developer notes"/><category scheme="http://www.blogger.com/atom/ns#" term="online quiz"/><category scheme="http://www.blogger.com/atom/ns#" term="quiz"/><title type='text'>Developer notes for the Adventure Game Missing Word Quiz</title><content type='html'>&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;span&gt;This project took quite a long time and was a bit frustrating, for similar reasons to those I encountered when developing &lt;/span&gt;&lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-and-wildcards-oh-my.html&quot;&gt;Noughts and Crosses and Wildards, Oh My!&lt;/a&gt;&lt;span&gt; - for want of a better description, after a while the AI seemed to get &quot;confused&quot; about what was happening, and in fixing an issue, it messed up something that was working perfectly well before. (Admittedly I have encountered this phenomenon many times before with normal coding!) I used Gemini for this project - although in retrospect I should have used 2.5 Pro instead of 2.5 Flash - it has the advantage over ChatGPT that you can preview the running code in the console, which is a nice time-saver for testing.&lt;/span&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;As with most of the other projects, the initial phase got something working along the lines of what I wanted very quickly - in this case particularly, with gathering a lot of data and structuring it for use within the web page, the time-saving was fantastic. Then once again, just on the cusp if it becoming what I wanted it to, things started to go wrong. In my secular job I use AI for data analysis, drafting documentation and prototyping - and this is still where I believe the key benefits of AI exist at this stage, quickly putting something decent together that can be refined by humans (possibly with the assistance of AI, possibly without) - but that step from conceptualising to prototyping to the finished article seems, at present, to be a difficult hurdle.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;After trying and failing a few times to get it to the state I wanted it to, I asked AI to take everything I&#39;d already asked for and turn it into a comprehensive prompt to start the process from scratch (still in Gemini). This accomplished, I started a new conversation with this prompt and began again.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;As it turned out, there was still a long way to go. The game was working absolutely fine until I asked it to introduce an extra step to choose the game length (10, 20 or 30 questions) - something about the code logic was failing here, and I couldn&#39;t get it right. One of my rules for &lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html&quot;&gt;this experiment of using AI to create small JavaScript games&lt;/a&gt; was that absolutely everything had to be done by the AI - so I didn&#39;t investigate the code myself or make any changes. Eventually I had to abandon this idea and go with a straight set of 20 questions each time.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Again, when I asked it to add social media buttons it worked great, but when I wanted to add a tweak later, things started going wrong. Now I must admit, my knowledge of the limitations of vanilla JavaScript is very... well, you know, limited - so perhaps the AI itself was not entirely the problem here, although eventually I did get it to work how I wanted it to (or at least, I think I did).&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;There was also the odd case of some hallucinating publisher information - it came up with a fictitious publisher for my own Captain Disaster games, and when I pointed out this error, it hallucinated a different publisher! (By &quot;fictitious&quot; I mean it was not the publisher for the games, not that the publisher itself didn&#39;t exist.) I solved that - I think! - by telling it to use the developer name for indie games where no publisher was specified.&amp;nbsp;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I ended up using about 50 prompts to reach this stage, and I realise now that I added things at early iterations that I should probably have removed (or realised would not be needed later) - for instance, I started by getting it to pull the game titles of just the major series first - Monkey Island, Broken Sword, Tex Murphy, King&#39;s / Space / Police Quest - which obviously created a lot of duplication in my early tests. So, I added the key title words to the exclusion list (so they wouldn&#39;t be repeatedly used when the game was played) - this made sense at the time with a database of about 30 games, but with it expanded out to the current list of 217, it would actually be better to take them out of the exclusion list. (I&#39;m a little reluctant to do this now, given that previous simple requests have caused other things to fail!)&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;All in all, this has been an interesting development project and I&#39;m reasonably happy with the end result (not saying I won&#39;t necessarily tweak it occasionally, of course!) It&#39;s been interesting comparing the strengths and weaknesses of this development methodology compared to my normal coding efforts. Other AIs - especially paid ones or those developed specifically for coding modalities - may be better, but for the purposes of my experiment, I&#39;m only using free models. There are ways in which AI can aid the coding flow, but certainly at this point any idea of AI &lt;b&gt;&lt;i&gt;replacing &lt;/i&gt;&lt;/b&gt;that flow seem a long way off (I&#39;m not saying it would be desirable for that to happen, I&#39;m just considering what is possible).&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;If anyone else out there wants to create apps with AI, it is certainly possible - but being able to clearly states your requirements and have an understanding of the underlying logic in the code, even if not the code itself, is important. I guess this was actually a moderately ambitious project given my self-imposed limitations, but it&#39;s something I&#39;d had in mind to do for quite some time.&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Anyway, I&#39;m probably going to leave AI alone for a while as I have lots of voice lines to process for &lt;a href=&quot;https://store.steampowered.com/app/2242270/Captain_Disaster_and_The_Two_Worlds_of_Riskara/&quot;&gt;Captain Disaster and the Two Worlds of Riskara&lt;/a&gt; (no AI code, art, music, voices etc in that one!! 😎 Just lots... and lots... and lots of work...)&lt;/span&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/6707418149807835983/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/developer-notes-for-adventure-game.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6707418149807835983'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6707418149807835983'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/developer-notes-for-adventure-game.html' title='Developer notes for the Adventure Game Missing Word Quiz'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-344187867678066412</id><published>2025-07-28T05:23:00.000-07:00</published><updated>2025-07-29T02:17:12.349-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="adventure game"/><category scheme="http://www.blogger.com/atom/ns#" term="missing word"/><category scheme="http://www.blogger.com/atom/ns#" term="online quiz"/><category scheme="http://www.blogger.com/atom/ns#" term="point and click"/><category scheme="http://www.blogger.com/atom/ns#" term="quiz"/><title type='text'>Adventure Game Missing Word Quiz</title><content type='html'>
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;&lt;/meta&gt;
    &lt;meta content=&quot;width=device-width, initial-scale=1.0&quot; name=&quot;viewport&quot;&gt;&lt;/meta&gt;
    &lt;title&gt;Adventure Game Title Challenge&lt;/title&gt;
    &lt;!-- Tailwind CSS CDN --&gt;
    &lt;script src=&quot;https://cdn.tailwindcss.com&quot;&gt;&lt;/script&gt;
    &lt;!-- Google Fonts - Inter --&gt;
    &lt;link href=&quot;https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700&amp;amp;display=swap&quot; rel=&quot;stylesheet&quot;&gt;&lt;/link&gt;
    &lt;style&gt;
        body {
            font-family: &#39;Inter&#39;, sans-serif;
            background-color: #1a202c; /* Dark theme background */
            color: #e2e8f0; /* Light text color */
            display: flex;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            padding: 1rem;
            box-sizing: border-box;
        }
        .game-container {
            background-color: #2d3748; /* Darker background for container */
            border-radius: 1.5rem; /* More rounded corners */
            padding: 2rem;
            box-shadow: 0 10px 15px rgba(0, 0, 0, 0.5);
            max-width: 600px;
            width: 100%;
            display: flex;
            flex-direction: column;
            gap: 1.5rem;
        }
        .title-header {
            font-size: 2.25rem; /* text-4xl */
            font-weight: 700; /* font-bold */
            text-align: center;
            color: #63b3ed; /* Blue for title */
            margin-bottom: 1rem;
        }
        .score-display {
            font-size: 1.25rem; /* text-xl */
            font-weight: 600; /* font-semibold */
            text-align: right;
            color: #a0aec0; /* Gray text */
        }
        .question-box {
            background-color: #4a5568; /* Even darker for question box */
            border-radius: 0.75rem; /* rounded-xl */
            padding: 1.5rem;
            text-align: center;
            display: flex;
            flex-direction: column;
            gap: 0.75rem;
        }
        .masked-title {
            font-size: 1.75rem; /* text-3xl */
            font-weight: 700; /* font-bold */
            color: #e2e8f0;
        }
        .game-info {
            font-size: 1rem; /* text-base */
            color: #cbd5e0;
        }
        .options-grid {
            display: grid;
            grid-template-columns: 1fr;
            gap: 1rem;
        }
        @media (min-width: 640px) { /* sm breakpoint */
            .options-grid {
                grid-template-columns: 1fr 1fr;
            }
        }
        .option-button {
            background-color: #4299e1; /* Blue button */
            color: white;
            padding: 1rem 1.5rem;
            border-radius: 0.75rem; /* rounded-xl */
            font-size: 1.125rem; /* text-lg */
            font-weight: 600; /* font-semibold */
            cursor: pointer;
            transition: background-color 0.3s ease, transform 0.2s ease;
            border: none;
            outline: none;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
        }
        .option-button:hover:not(:disabled) {
            background-color: #3182ce; /* Darker blue on hover */
            transform: translateY(-2px);
        }
        .option-button:disabled {
            opacity: 0.7;
            cursor: not-allowed;
        }
        .option-button.correct {
            background-color: #48bb78; /* Green for correct */
        }
        .option-button.incorrect {
            background-color: #e53e3e; /* Red for incorrect */
        }
        .feedback-message {
            font-size: 1.125rem; /* text-lg */
            font-weight: 600;
            text-align: center;
            min-height: 1.5rem; /* To prevent layout shift */
        }
        .next-button {
            background-color: #ed8936; /* Orange button */
            color: white;
            padding: 1rem 1.5rem;
            border-radius: 0.75rem; /* rounded-xl */
            font-size: 1.25rem; /* text-xl */
            font-weight: 700; /* font-bold */
            cursor: pointer;
            transition: background-color 0.3s ease, transform 0.2s ease;
            border: none;
            outline: none;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.3);
            width: 100%;
        }
        .next-button:hover:not(:disabled) {
            background-color: #dd6b20; /* Darker orange on hover */
            transform: translateY(-2px);
        }
        .next-button:disabled {
            opacity: 0.7;
            cursor: not-allowed;
        }
        /* Specific styles for share buttons with !important to override external CSS */
        .share-buttons {
            display: flex !important; /* Ensure flexbox layout */
            flex-direction: column !important; /* Arrange items vertically */
            justify-content: center !important;
            align-items: center !important; /* Center buttons horizontally if they don&#39;t take full width */
            gap: 0.75rem !important; /* Vertical spacing between buttons */
            margin-top: 1.5rem !important;
            /* Added to explicitly control positioning if Blogger adds absolute/fixed */
            position: relative !important;
            z-index: auto !important;
            width: 100% !important; /* Ensure the container takes full width */
        }
        .share-button {
            width: 100% !important; /* Make buttons take full width of their container */
            padding: 1.8rem 1.25rem !important; /* Increased vertical padding for more height */
            border-radius: 0.5rem !important;
            font-weight: 600 !important;
            cursor: pointer !important;
            transition: background-color 0.3s ease, transform 0.2s ease !important;
            border: none !important;
            outline: none !important;
            box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2) !important;
            /* Ensure text is visible and not replaced by icons */
            display: inline-block !important;
            text-indent: 0 !important;
            overflow: visible !important;
            white-space: nowrap !important; /* Prevent text wrapping */
            text-align: center !important; /* Center text within the button */
            font-size: 1rem !important; /* Ensure font size is consistent */
            line-height: normal !important; /* Reset line height */
            /* Added to explicitly remove any background images or icon fonts */
            background-image: none !important;
            font-family: &#39;Inter&#39;, sans-serif !important; /* Force Inter font */
            vertical-align: middle !important; /* Adjust vertical alignment */
            /* Ensure no padding/margin is collapsing or causing issues */
            margin: 0 !important;
            box-sizing: border-box !important; /* Ensure padding is included in width/height */
        }
        .share-button.twitter {
            background-color: #1DA1F2 !important;
            color: white !important;
        }
        .share-button.twitter:hover {
            background-color: #0c85d0 !important;
            transform: translateY(-1px) !important;
        }
        .share-button.bluesky {
            background-color: #0077ff !important; /* A common Bluesky blue */
            color: white !important;
        }
        .share-button.bluesky:hover {
            background-color: #005bb8 !important;
            transform: translateY(-1px) !important;
        }
        .share-button.mastodon {
            background-color: #6364FF !important; /* A common Mastodon purple */
            color: white !important;
        }
        .share-button.mastodon:hover {
            background-color: #4a4ad8 !important;
            transform: translateY(-1px) !important;
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;div class=&quot;game-container&quot;&gt;
        &lt;h1 class=&quot;title-header&quot;&gt;Adventure Game Title Challenge&lt;/h1&gt;
        &lt;div class=&quot;score-display&quot; id=&quot;score-display&quot;&gt;Score: 0/0&lt;/div&gt;

        &lt;div class=&quot;question-box&quot; id=&quot;question-box&quot;&gt;
            &lt;div class=&quot;masked-title&quot; id=&quot;masked-title&quot;&gt;[____]&lt;/div&gt;
            &lt;div class=&quot;game-info&quot; id=&quot;game-info&quot;&gt;Year: N/A | Publisher: N/A&lt;/div&gt;
        &lt;/div&gt;

        &lt;div class=&quot;options-grid&quot; id=&quot;options-grid&quot;&gt;
            &lt;!-- Option buttons will be inserted here by JavaScript --&gt;
        &lt;/div&gt;

        &lt;div class=&quot;feedback-message&quot; id=&quot;feedback-message&quot;&gt;&lt;/div&gt;

        &lt;button class=&quot;next-button&quot; disabled=&quot;&quot; id=&quot;next-button&quot;&gt;Next Question&lt;/button&gt;

        &lt;!-- Moved share-buttons here to be after the next-button --&gt;
        &lt;div class=&quot;share-buttons&quot; id=&quot;share-buttons&quot;&gt;
            &lt;button class=&quot;share-button twitter&quot; id=&quot;share-twitter&quot;&gt;Share on X&lt;/button&gt;
            &lt;button class=&quot;share-button bluesky&quot; id=&quot;share-bluesky&quot;&gt;Share on Bluesky&lt;/button&gt;
            &lt;button class=&quot;share-button mastodon&quot; id=&quot;share-mastodon&quot;&gt;Share on Mastodon&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
        // Raw list of all game titles with metadata, with verified publishers
        const rawGameTitles = [
            // Monkey Island Series
            { title: &quot;The Secret of Monkey Island&quot;, year: 1990, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;Monkey Island 2: LeChuck&#39;s Revenge&quot;, year: 1991, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;The Curse of Monkey Island&quot;, year: 1997, publisher: &quot;LucasArts&quot; },
            { title: &quot;Escape from Monkey Island&quot;, year: 2000, publisher: &quot;LucasArts&quot; },
            { title: &quot;Tales of Monkey Island: Chapter 1 - Launch of the Screaming Narwhal&quot;, year: 2009, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Tales of Monkey Island: Chapter 2 - The Siege of Spinner Cay&quot;, year: 2009, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Tales of Monkey Island: Chapter 3 - Lair of the Leviathan&quot;, year: 2009, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Tales of Monkey Island: Chapter 4 - The Trial and Tribulations of Guybrush Threepwood&quot;, year: 2009, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Tales of Monkey Island: Chapter 5 - Rise of the Pirate God&quot;, year: 2009, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Return to Monkey Island&quot;, year: 2022, publisher: &quot;Devolver Digital&quot; },

            // Broken Sword Series
            { title: &quot;Broken Sword: The Shadow of the Templars&quot;, year: 1996, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Broken Sword II: The Smoking Mirror&quot;, year: 1997, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Broken Sword: The Sleeping Dragon&quot;, year: 2003, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Broken Sword: The Angel of Death&quot;, year: 2006, publisher: &quot;Microids&quot; },
            { title: &quot;Broken Sword 5: The Serpent&#39;s Curse&quot;, year: 2013, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Broken Sword: Parzival&#39;s Stone&quot;, year: 2025, publisher: &quot;Revolution Software&quot; },

            // Tex Murphy Series
            { title: &quot;Mean Streets&quot;, year: 1989, publisher: &quot;Access Software&quot; },
            { title: &quot;Martian Memorandum&quot;, year: 1991, publisher: &quot;Access Software&quot; },
            { title: &quot;Under a Killing Moon&quot;, year: 1994, publisher: &quot;Access Software&quot; },
            { title: &quot;The Pandora Directive&quot;, year: 1996, publisher: &quot;Access Software&quot; },
            { title: &quot;Overseer&quot;, year: 1998, publisher: &quot;Access Software&quot; },
            { title: &quot;Tesla Effect: A Tex Murphy Adventure&quot;, year: 2014, publisher: &quot;Atlus&quot; },

            // King&#39;s Quest Series
            { title: &quot;King&#39;s Quest I: Quest for the Crown&quot;, year: 1984, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest II: Romancing the Throne&quot;, year: 1985, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest III: To Heir Is Human&quot;, year: 1986, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest IV: The Perils of Rosella&quot;, year: 1998, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest V: Absence Makes the Heart Go Yonder!&quot;, year: 1990, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest VI: Heir Today, Gone Tomorrow&quot;, year: 1992, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest VII: The Princeless Bride&quot;, year: 1994, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest: Mask of Eternity&quot;, year: 1998, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;King&#39;s Quest (2015)&quot;, year: 2015, publisher: &quot;Sierra Entertainment&quot; },

            // Space Quest Series
            { title: &quot;Space Quest: The Sarien Encounter&quot;, year: 1986, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Space Quest II: Vohaul&#39;s Revenge&quot;, year: 1987, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Space Quest III: The Pirates of Pestulon&quot;, year: 1989, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Space Quest IV: Roger Wilco and the Time Rippers&quot;, year: 1991, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Space Quest V: The Next Mutation&quot;, year: 1993, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Space Quest 6: Roger Wilco in The Spinal Frontier&quot;, year: 1995, publisher: &quot;Sierra On-Line&quot; },

            // Police Quest Series
            { title: &quot;Police Quest: In Pursuit of the Death Angel&quot;, year: 1987, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Police Quest II: The Vengeance&quot;, year: 1988, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Police Quest III: The Kindred&quot;, year: 1991, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Police Quest IV: Open Season&quot;, year: 1993, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Police Quest: SWAT&quot;, year: 1995, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Police Quest: SWAT 2&quot;, year: 1998, publisher: &quot;Sierra On-Line&quot; },

            // Ankh Series
            { title: &quot;Ankh: The Tales of Mystery&quot;, year: 2005, publisher: &quot;Deck13 Interactive&quot; },
            { title: &quot;Ankh: Heart of Osiris&quot;, year: 2006, publisher: &quot;Deck13 Interactive&quot; },
            { title: &quot;Ankh: Battle of the Gods&quot;, year: 2007, publisher: &quot;Deck13 Interactive&quot; },

            // Ben Jordan: Paranormal Investigator Series
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 1: In Search of the Skunk-Ape&quot;, year: 2004, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 2: The Lost Galleon of the Salton Sea&quot;, year: 2004, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 3: The Sorceress of Smailholm&quot;, year: 2005, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paran paranormal Investigator - Case 4: Horror at Number 50&quot;, year: 2006, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 5: Land of the Rising Dead&quot;, year: 2007, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 6: Scourge of the Sea People&quot;, year: 2008, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 7: The Cardinal Sins&quot;, year: 2009, publisher: &quot;Grundislav Games&quot; },
            { title: &quot;Ben Jordan: Paranormal Investigator - Case 8: Relics of the Past&quot;, year: 2012, publisher: &quot;Grundislav Games&quot; },

            // Wadjet Eye Games (Publisher)
            { title: &quot;The Shivah&quot;, year: 2006, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Blackwell Unbound&quot;, year: 2007, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Emerald City Confidential&quot;, year: 2009, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Puzzle Bots&quot;, year: 2010, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Gemini Rue&quot;, year: 2011, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Da New Guys&quot;, year: 2012, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Resonance&quot;, year: 2012, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;A Golden Wake&quot;, year: 2014, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;The Blackwell Epiphany&quot;, year: 2014, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;The Blackwell Deception&quot;, year: 2011, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;The Blackwell Convergence&quot;, year: 2009, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;The Blackwell Legacy&quot;, year: 2006, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Primordia&quot;, year: 2012, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Shardlight&quot;, year: 2016, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Technobabylon&quot;, year: 2015, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Strangeland&quot;, year: 2021, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;The Excavation of Hob&#39;s Barrow&quot;, year: 2022, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Unavowed&quot;, year: 2018, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Old Skies&quot;, year: 2025, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Nighthawks&quot;, year: 2025, publisher: &quot;Wadjet Eye Games&quot; },

            // Captain Disaster Series
            { title: &quot;Captain Disaster in: The Dark Side of the Moon&quot;, year: 2012, publisher: &quot;Team Disaster&quot; },
            { title: &quot;Captain Disaster in: Death Has A Million Stomping Boots&quot;, year: 2015, publisher: &quot;Team Disaster&quot; },
            { title: &quot;Captain Disaster and The Two Worlds of Riskara&quot;, year: 2025, publisher: &quot;Team Disaster&quot; },

            // LucasArts Standalone
            { title: &quot;The Dig&quot;, year: 1995, publisher: &quot;LucasArts&quot; },

            // Indiana Jones Games
            { title: &quot;Indiana Jones and the Last Crusade&quot;, year: 1989, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;Indiana Jones and the Fate of Atlantis&quot;, year: 1992, publisher: &quot;LucasArts&quot; },
            { title: &quot;Indiana Jones and the Infernal Machine&quot;, year: 1999, publisher: &quot;LucasArts&quot; },
            { title: &quot;Indiana Jones and the Emperor&#39;s Tomb&quot;, year: 2003, publisher: &quot;LucasArts&quot; },

            // Infocom Text Adventures
            { title: &quot;Zork I: The Great Underground Empire&quot;, year: 1980, publisher: &quot;Infocom&quot; },
            { title: &quot;Zork II: The Wizard of Frobozz&quot;, year: 1981, publisher: &quot;Infocom&quot; },
            { title: &quot;Zork III: The Dungeon Master&quot;, year: 1982, publisher: &quot;Infocom&quot; },
            { title: &quot;Deadline&quot;, year: 1982, publisher: &quot;Infocom&quot; },
            { title: &quot;Starcross&quot;, year: 1982, publisher: &quot;Infocom&quot; },
            { title: &quot;Suspended&quot;, year: 1983, publisher: &quot;Infocom&quot; },
            { title: &quot;Planetfall&quot;, year: 1983, publisher: &quot;Infocom&quot; },
            { title: &quot;The Witness&quot;, year: 1983, publisher: &quot;Infocom&quot; },
            { title: &quot;Enchanter&quot;, year: 1983, publisher: &quot;Infocom&quot; },
            { title: &quot;Infidel&quot;, year: 1983, publisher: &quot;Infocom&quot; },
            { title: &quot;Sorcerer&quot;, year: 1984, publisher: &quot;Infocom&quot; },
            { title: &quot;Seastalker&quot;, year: 1984, publisher: &quot;Infocom&quot; },
            { title: &quot;Cutthroats&quot;, year: 1984, publisher: &quot;Infocom&quot; },
            { title: &quot;The Hitchhiker&#39;s Guide to the Galaxy&quot;, year: 1984, publisher: &quot;Infocom&quot; },
            { title: &quot;A Mind Forever Voyaging&quot;, year: 1985, publisher: &quot;Infocom&quot; },
            { title: &quot;Wishbringer&quot;, year: 1985, publisher: &quot;Infocom&quot; },
            { title: &quot;Spellbreaker&quot;, year: 1985, publisher: &quot;Infocom&quot; },
            { title: &quot;Ballyhoo&quot;, year: 1986, publisher: &quot;Infocom&quot; },
            { title: &quot;Trinity&quot;, year: 1986, publisher: &quot;Infocom&quot; },
            { title: &quot;Moonmist&quot;, year: 1986, publisher: &quot;Infocom&quot; },
            { title: &quot;Hollywood Hijinx&quot;, year: 1986, publisher: &quot;Infocom&quot; },
            { title: &quot;Leather Goddesses of Phobos&quot;, year: 1986, publisher: &quot;Infocom&quot; },
            { title: &quot;Border Zone&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Bureaucracy&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Stationfall&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Nord and Bert Couldn&#39;t Make Head or Tail of It&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;The Lurking Horror&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Plundered Hearts&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Beyond Zork&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Sherlock: The Riddle of the Crown Jewels&quot;, year: 1987, publisher: &quot;Infocom&quot; },
            { title: &quot;Zork Zero: The Revenge of Megaboz&quot;, year: 1988, publisher: &quot;Infocom&quot; },
            { title: &quot;Journey&quot;, year: 1989, publisher: &quot;Infocom&quot; },
            { title: &quot;Arthur: The Quest for Excalibur&quot;, year: 1989, publisher: &quot;Infocom&quot; },

            // Level 9 Computing Games
            { title: &quot;Colossal Adventure&quot;, year: 1981, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Adventure Quest&quot;, year: 1982, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Dungeon Adventure&quot;, year: 1982, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Emerald Isle&quot;, year: 1983, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Lords of Time&quot;, year: 1983, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Red Moon&quot;, year: 1984, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;The Price of Magik&quot;, year: 1984, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Return to Eden&quot;, year: 1984, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Snowball&quot;, year: 1983, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;The Worm in Paradise&quot;, year: 1985, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;The Secret Diary of Adrian Mole Aged 13¾&quot;, year: 1985, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;The Archers&quot;, year: 1985, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;The Growing Pains of Adrian Mole&quot;, year: 1987, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Knight Orc&quot;, year: 1986, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Gnome Ranger&quot;, year: 1987, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Time and Magik&quot;, year: 1988, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Ingrid&#39;s Back!&quot;, year: 1988, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Scapeghost&quot;, year: 1989, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Erik the Viking&quot;, year: 1989, publisher: &quot;Level 9 Computing&quot; },
            { title: &quot;Champion of the Raj&quot;, year: 1991, publisher: &quot;Level 9 Computing&quot; },

            // Magnetic Scrolls Games
            { title: &quot;The Pawn&quot;, year: 1985, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;The Guild of Thieves&quot;, year: 1987, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;Jinxter&quot;, year: 1987, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;Corruption&quot;, year: 1988, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;Fish!&quot;, year: 1988, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;Myth&quot;, year: 1989, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;Wonderland&quot;, year: 1990, publisher: &quot;Magnetic Scrolls&quot; },
            { title: &quot;The Legacy: Realm of Terror&quot;, year: 1993, publisher: &quot;Magnetic Scrolls&quot; },

            // Recent Indie/Freeware Point &amp; Click (approx. 2015-2025)
            { title: &quot;Thimbleweed Park&quot;, year: 2017, publisher: &quot;Terrible Toybox&quot; },
            { title: &quot;Kathy Rain&quot;, year: 2016, publisher: &quot;Raw Fury&quot; },
            { title: &quot;The Darkside Detective&quot;, year: 2017, publisher: &quot;Akupara Games&quot; },
            { title: &quot;Norco&quot;, year: 2022, publisher: &quot;Raw Fury&quot; },
            { title: &quot;The Case of the Golden Idol&quot;, year: 2022, publisher: &quot;Playstack&quot; },
            { title: &quot;Return of the Obra Dinn&quot;, year: 2018, publisher: &quot;3909 LLC&quot; },
            { title: &quot;Unforeseen Incidents&quot;, year: 2018, publisher: &quot;Application Systems Heidelberg&quot; },
            { title: &quot;Lucy Dreaming&quot;, year: 2022, publisher: &quot;Tall Story Games&quot; },
            { title: &quot;Beyond a Steel Sky&quot;, year: 2020, publisher: &quot;Revolution Software&quot; },
            { title: &quot;The Blind Prophet&quot;, year: 2020, publisher: &quot;Plug In Digital&quot; },
            { title: &quot;Blood Nova&quot;, year: 2023, publisher: &quot;Cosmic Void&quot; },
            { title: &quot;Call of the Sea&quot;, year: 2020, publisher: &quot;Raw Fury&quot; },
            { title: &quot;Chicken Police: Paint It Red!&quot;, year: 2020, publisher: &quot;HandyGames&quot; },
            { title: &quot;Chuchel&quot;, year: 2018, publisher: &quot;Amanita Design&quot; },
            { title: &quot;Contradiction: Spot the Liar!&quot;, year: 2015, publisher: &quot;Baggy Cat Ltd&quot; },
            { title: &quot;Detective Di: The Silk Rose Murders&quot;, year: 2019, publisher: &quot;Nupixo Games&quot; },
            { title: &quot;Gibbous: A Cthulhu Adventure&quot;, year: 2019, publisher: &quot;Stuck In Attic&quot; },
            { title: &quot;Heaven&#39;s Vault&quot;, year: 2019, publisher: &quot;Inkle&quot; },
            { title: &quot;Immortality&quot;, year: 2022, publisher: &quot;Half Mermaid Productions&quot; },
            { title: &quot;Inspector Waffles&quot;, year: 2021, publisher: &quot;Hitcents&quot; },
            { title: &quot;Lacuna&quot;, year: 2021, publisher: &quot;Assemble Entertainment&quot; },
            { title: &quot;The Last Door&quot;, year: 2013, publisher: &quot;Phoenix Online Studios&quot; },
            { title: &quot;Leisure Suit Larry: Wet Dreams Don&#39;t Dry&quot;, year: 2018, publisher: &quot;Assemble Entertainment&quot; },
            { title: &quot;Leisure Suit Larry: Wet Dreams Dry Twice&quot;, year: 2020, publisher: &quot;Assemble Entertainment&quot; },
            { title: &quot;Nelly Cootalot: The Fowl Fleet&quot;, year: 2016, publisher: &quot;Application Systems Heidelberg&quot; },
            { title: &quot;Neocab&quot;, year: 2019, publisher: &quot;Fellow Traveller&quot; },
            { title: &quot;Pentiment&quot;, year: 2022, publisher: &quot;Xbox Game Studios&quot; },
            { title: &quot;Röki&quot;, year: 2020, publisher: &quot;United Label&quot; },
            { title: &quot;Saint Kotar&quot;, year: 2021, publisher: &quot;Soedesco&quot; },
            { title: &quot;She Sees Red&quot;, year: 2019, publisher: &quot;Gambitious&quot; },
            { title: &quot;Stasis: Bone Totem&quot;, year: 2023, publisher: &quot;Daedalic Entertainment&quot; },
            { title: &quot;Tangle Tower&quot;, year: 2019, publisher: &quot;SFB Games&quot; },
            { title: &quot;Tsioque&quot;, year: 2018, publisher: &quot;OhNoo Studio&quot; },
            { title: &quot;Voodoo Detective&quot;, year: 2022, publisher: &quot;Voodoo Detective&quot; },
            { title: &quot;Willy Morgan and the Curse of Bone Town&quot;, year: 2020, publisher: &quot;VLG Publishing&quot; },
            { title: &quot;Whispers of a Machine&quot;, year: 2019, publisher: &quot;Raw Fury&quot; },
            { title: &quot;The Journey Down: Chapter One&quot;, year: 2010, publisher: &quot;SkyGoblin&quot; },
            { title: &quot;The Journey Down: Chapter Two&quot;, year: 2014, publisher: &quot;SkyGoblin&quot; },
            { title: &quot;The Journey Down: Chapter Three&quot;, year: 2017, publisher: &quot;SkyGoblin&quot; },
            { title: &quot;Milk outside a bag of milk outside a bag of milk&quot;, year: 2021, publisher: &quot;Nikita Kryukov&quot; },
            { title: &quot;Milk inside a bag of milk inside a bag of milk&quot;, year: 2020, publisher: &quot;Nikita Kryukov&quot; },
            { title: &quot;Disco Elysium&quot;, year: 2019, publisher: &quot;ZA/UM&quot; },
            { title: &quot;Life is Strange&quot;, year: 2015, publisher: &quot;Square Enix&quot; },
            { title: &quot;The Wolf Among Us&quot;, year: 2013, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Sam &amp; Max Save the World&quot;, year: 2006, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Sam &amp; Max Beyond Time and Space&quot;, year: 2007, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Sam &amp; Max The Devil&#39;s Playhouse&quot;, year: 2010, publisher: &quot;Telltale Games&quot; },
            { title: &quot;The Walking Dead: Season One&quot;, year: 2012, publisher: &quot;Telltale Games&quot; },
            { title: &quot;The Walking Dead: Season Two&quot;, year: 2013, publisher: &quot;Telltale Games&quot; },
            { title: &quot;The Walking Dead: A New Frontier&quot;, year: 2016, publisher: &quot;Telltale Games&quot; },
            { title: &quot;The Walking Dead: The Final Season&quot;, year: 2018, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Batman: The Telltale Series&quot;, year: 2016, publisher: &quot;Telltale Games&quot; },
            { title: &quot;Batman: The Enemy Within&quot;, year: 2017, publisher: &quot;Telltale Games&quot; },
            { title: &quot;The Coma: Recut&quot;, year: 2017, publisher: &quot;Digerati&quot; },
            { title: &quot;The Coma 2: Vicious Sisters&quot;, year: 2020, publisher: &quot;Digerati&quot; },
            { title: &quot;The Coma 3: Labyrinth of Lies&quot;, year: 2023, publisher: &quot;Digerati&quot; },
            { title: &quot;Detention&quot;, year: 2017, publisher: &quot;Red Candle Games&quot; },
            { title: &quot;Devotion&quot;, year: 2019, publisher: &quot;Red Candle Games&quot; },
            { title: &quot;Gris&quot;, year: 2018, publisher: &quot;Devolver Digital&quot; },
            { title: &quot;What Remains of Edith Finch&quot;, year: 2017, publisher: &quot;Annapurna Interactive&quot; },
            { title: &quot;Firewatch&quot;, year: 2016, publisher: &quot;Panic Inc.&quot; },
            { title: &quot;Oxenfree&quot;, year: 2016, publisher: &quot;Night School Studio&quot; },
            { title: &quot;Night in the Woods&quot;, year: 2017, publisher: &quot;Finji&quot; },
            { title: &quot;Mutropolis&quot;, year: 2021, publisher: &quot;Pirita Studio&quot; },
            { title: &quot;The Procession to Calvary&quot;, year: 2020, publisher: &quot;Joe Richardson&quot; },
            { title: &quot;Lamplight City&quot;, year: 2018, publisher: &quot;Application Systems Heidelberg&quot; },
            { title: &quot;Bear With Me&quot;, year: 2016, publisher: &quot;Modus Games&quot; },
            { title: &quot;Truberbrook&quot;, year: 2019, publisher: &quot;Headup Games&quot; },
            { title: &quot;Detective Gallo&quot;, year: 2019, publisher: &quot;Adventure Productions&quot; },
            { title: &quot;The Will of Arthur Flabbington&quot;, year: 2021, publisher: &quot;Wormwood Studios&quot; },
            { title: &quot;Unusual Findings&quot;, year: 2022, publisher: &quot;Epic Llama Games&quot; },
            { title: &quot;Darkestville Castle&quot;, year: 2017, publisher: &quot;Buka Entertainment&quot; },
            { title: &quot;Loco Motive&quot;, year: 2023, publisher: &quot;Chucklefish&quot; },
            { title: &quot;Crimson Diamond&quot;, year: 2024, publisher: &quot;Julia Minamata&quot; },
            { title: &quot;Slender Threads&quot;, year: 2025, publisher: &quot;Bad Vertices&quot; },
            { title: &quot;Rosewater&quot;, year: 2025, publisher: &quot;Wadjet Eye Games&quot; },
            { title: &quot;Elroy and the Aliens&quot;, year: 2025, publisher: &quot;Adventure Productions&quot; },
            { title: &quot;The Flayed Man&quot;, year: 2025, publisher: &quot;SnoringDogGames&quot; },
            { title: &quot;Simon the Sorcerer: Origins&quot;, year: 2025, publisher: &quot;Adventure Soft&quot; },
            { title: &quot;The Red Strings Club&quot;, year: 2018, publisher: &quot;Devolver Digital&quot; },
            { title: &quot;VirtuaVerse&quot;, year: 2020, publisher: &quot;Mastertronic&quot; },
            { title: &quot;Chinatown Detective Agency&quot;, year: 2022, publisher: &quot;Humble Games&quot; },
            { title: &quot;State of Mind&quot;, year: 2018, publisher: &quot;Daedalic Entertainment&quot; },
            { title: &quot;Dex&quot;, year: 2015, publisher: &quot;BadLand Games&quot; },
            { title: &quot;Shadowgate (2014)&quot;, year: 2014, publisher: &quot;Reverb Triple XP&quot; },
            { title: &quot;Obduction&quot;, year: 2016, publisher: &quot;Cyan Worlds&quot; },
            { title: &quot;Call of Cthulhu (2018)&quot;, year: 2018, publisher: &quot;Focus Home Interactive&quot; },
            { title: &quot;Syberia 3&quot;, year: 2017, publisher: &quot;Microids&quot; },
            { title: &quot;Syberia: The World Before&quot;, year: 2022, publisher: &quot;Microids&quot; },
            { title: &quot;Broken Age&quot;, year: 2015, publisher: &quot;Double Fine Productions&quot; },
            { title: &quot;Dropsy&quot;, year: 2015, publisher: &quot;Devolver Digital&quot; },
            { title: &quot;Fran Bow&quot;, year: 2015, publisher: &quot;Killmonday Games&quot; },
            { title: &quot;Little Misfortune&quot;, year: 2019, publisher: &quot;Killmonday Games&quot; },
            { title: &quot;Samorost 3&quot;, year: 2016, publisher: &quot;Amanita Design&quot; },
            { title: &quot;Creaks&quot;, year: 2020, publisher: &quot;Amanita Design&quot; },
            { title: &quot;Pilgrims&quot;, year: 2019, publisher: &quot;Amanita Design&quot; },
            { title: &quot;Deponia Doomsday&quot;, year: 2016, publisher: &quot;Daedalic Entertainment&quot; },
            { title: &quot;Silence&quot;, year: 2016, publisher: &quot;Daedalic Entertainment&quot; },
            { title: &quot;Anna&#39;s Quest&quot;, year: 2015, publisher: &quot;Daedalic Entertainment&quot; },
            { title: &quot;Styx: Shards of Darkness&quot;, year: 2017, publisher: &quot;Focus Home Interactive&quot; },
            { title: &quot;The Raven: Remastered&quot;, year: 2018, publisher: &quot;THQ Nordic&quot; },
            { title: &quot;Black Mirror (2017)&quot;, year: 2017, publisher: &quot;THQ Nordic&quot; },
            { title: &quot;Agatha Christie - The ABC Murders&quot;, year: 2016, publisher: &quot;Microids&quot; },
            { title: &quot;Agatha Christie - Hercule Poirot: The First Cases&quot;, year: 2021, publisher: &quot;Microids&quot; },
            { title: &quot;Agatha Christie - Hercule Poirot: The London Case&quot;, year: 2023, publisher: &quot;Microids&quot; },
            { title: &quot;Alfred Hitchcock - Vertigo&quot;, year: 2021, publisher: &quot;Microids&quot; },
            { title: &quot;Blacksad: Under the Skin&quot;, year: 2019, publisher: &quot;Microids&quot; },
            { title: &quot;Yesterday Origins&quot;, year: 2016, publisher: &quot;Microids&quot; },
            { title: &quot;Amerzone: The Explorer&#39;s Legacy (Remake)&quot;, year: 2025, publisher: &quot;Microids&quot; },
            { title: &quot;Centum&quot;, year: 2025, publisher: &quot;Serenity Forge&quot; },
            { title: &quot;Mutants Ate My Carrots&quot;, year: 2025, publisher: &quot;&quot; },
            { title: &quot;Shadows of the Afterland&quot;, year: 2025, publisher: &quot;Aruma Studios&quot; },
            { title: &quot;The Hand of Glory 2: Roots in the Sky&quot;, year: 2025, publisher: &quot;&quot; },
            { title: &quot;Whirlight – No Time To Trip&quot;, year: 2025, publisher: &quot;imaginarylab&quot; },
            // Added games from 80s-2000s
            { title: &quot;Maniac Mansion&quot;, year: 1987, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;Zak McKracken and the Alien Mindbenders&quot;, year: 1988, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;Loom&quot;, year: 1990, publisher: &quot;Lucasfilm Games&quot; },
            { title: &quot;Day of the Tentacle&quot;, year: 1993, publisher: &quot;LucasArts&quot; },
            { title: &quot;Sam &amp; Max Hit the Road&quot;, year: 1993, publisher: &quot;LucasArts&quot; },
            { title: &quot;Full Throttle&quot;, year: 1995, publisher: &quot;LucasArts&quot; },
            { title: &quot;Grim Fandango&quot;, year: 1998, publisher: &quot;LucasArts&quot; },
            { title: &quot;Gabriel Knight: Sins of the Fathers&quot;, year: 1993, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Gabriel Knight 2: The Beast Within&quot;, year: 1995, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Gabriel Knight 3: Blood of the Sacred, Blood of the Damned&quot;, year: 1999, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Myst&quot;, year: 1993, publisher: &quot;Broderbund&quot; },
            { title: &quot;Riven: The Sequel to Myst&quot;, year: 1997, publisher: &quot;Red Orb Entertainment&quot; },
            { title: &quot;Myst III: Exile&quot;, year: 2001, publisher: &quot;Ubi Soft&quot; },
            { title: &quot;The Longest Journey&quot;, year: 1999, publisher: &quot;Funcom&quot; },
            { title: &quot;Dreamfall: The Longest Journey&quot;, year: 2006, publisher: &quot;Funcom&quot; },
            { title: &quot;Syberia&quot;, year: 2002, publisher: &quot;Microids&quot; },
            { title: &quot;Syberia II&quot;, year: 2004, publisher: &quot;Microids&quot; },
            { title: &quot;Discworld&quot;, year: 1995, publisher: &quot;Psygnosis&quot; },
            { title: &quot;Discworld II: Missing Presumed...!&quot;, year: 1996, publisher: &quot;Psygnosis&quot; },
            { title: &quot;Simon the Sorcerer&quot;, year: 1993, publisher: &quot;Adventure Soft&quot; },
            { title: &quot;Simon the Sorcerer II: The Lion, the Wizard and the Wardrobe&quot;, year: 1995, publisher: &quot;Adventure Soft&quot; },
            { title: &quot;Beneath a Steel Sky&quot;, year: 1994, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Flight of the Amazon Queen&quot;, year: 1995, publisher: &quot;Interactive Binary Illusions&quot; },
            { title: &quot;Broken Sword: The Shadow of the Templars (Remastered)&quot;, year: 2009, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Broken Sword II: The Smoking Mirror (Remastered)&quot;, year: 2010, publisher: &quot;Revolution Software&quot; },
            { title: &quot;Blade Runner&quot;, year: 1997, publisher: &quot;Virgin Interactive&quot; },
            { title: &quot;Sanitarium&quot;, year: 1998, publisher: &quot;DreamCatcher Interactive&quot; },
            { title: &quot;Grim Fandango Remastered&quot;, year: 2015, publisher: &quot;Double Fine Productions&quot; },
            { title: &quot;Day of the Tentacle Remastered&quot;, year: 2016, publisher: &quot;Double Fine Productions&quot; },
            { title: &quot;Full Throttle Remastered&quot;, year: 2017, publisher: &quot;Double Fine Productions&quot; },
            { title: &quot;The Secret of Monkey Island: Special Edition&quot;, year: 2009, publisher: &quot;LucasArts&quot; },
            { title: &quot;Monkey Island 2 Special Edition: LeChuck&#39;s Revenge&quot;, year: 2010, publisher: &quot;LucasArts&quot; },
            { title: &quot;I Have No Mouth, and I Must Scream&quot;, year: 1995, publisher: &quot;Cyberdreams&quot; },
            { title: &quot;The 7th Guest&quot;, year: 1993, publisher: &quot;Virgin Games&quot; },
            { title: &quot;The 11th Hour&quot;, year: 1995, publisher: &quot;Virgin Interactive&quot; },
            { title: &quot;Phantasmagoria&quot;, year: 1995, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Phantasmagoria 2: A Puzzle of Flesh&quot;, year: 1996, publisher: &quot;Sierra On-Line&quot; },
            { title: &quot;Gabriel Knight: Sins of the Fathers (20th Anniversary Edition)&quot;, year: 2014, publisher: &quot;Pinkerton Road Studio&quot; },
            { title: &quot;Tex Murphy: Under a Killing Moon (Remastered)&quot;, year: 2014, publisher: &quot;Big Finish Games&quot; },
            { title: &quot;Tex Murphy: The Pandora Directive (Remastered)&quot;, year: 2014, publisher: &quot;Big Finish Games&quot; },
            { title: &quot;Tex Murphy: Overseer (Remastered)&quot;, year: 2014, publisher: &quot;Big Finish Games&quot; },
            { title: &quot;The Journeyman Project: Pegasus Prime&quot;, year: 1997, publisher: &quot;Presto Studios&quot; },
            { title: &quot;The Journeyman Project 2: Buried in Time&quot;, year: 1995, publisher: &quot;Sanctuary Woods&quot; },
            { title: &quot;The Journeyman Project 3: Legacy of Time&quot;, year: 1998, publisher: &quot;Red Orb Entertainment&quot; },
            { title: &quot;Riven (Remastered)&quot;, year: 2024, publisher: &quot;Cyan Worlds&quot; },
            { title: &quot;The Last Express&quot;, year: 1997, publisher: &quot;Broderbund&quot; },
            { title: &quot;Discworld Noir&quot;, year: 1999, publisher: &quot;GT Interactive&quot; },
            { title: &quot;Myst IV: Revelation&quot;, year: 2004, publisher: &quot;Ubi Soft&quot; },
            { title: &quot;Myst V: End of Ages&quot;, year: 2005, publisher: &quot;Ubi Soft&quot; },
            { title: &quot;Zork Nemesis: The Forbidden Lands&quot;, year: 1996, publisher: &quot;Activision&quot; },
            { title: &quot;Zork Grand Inquisitor&quot;, year: 1997, publisher: &quot;Activision&quot; },
            { title: &quot;Return to Zork&quot;, year: 1993, publisher: &quot;Activision&quot; },
        ];

        // Excluded words: common words, numbers, Roman numerals, edition/series suffixes.
        // Also includes common words that are part of series names to prevent them from being masked.
        const excludedWords = new Set([
            &quot;the&quot;, &quot;a&quot;, &quot;an&quot;, &quot;of&quot;, &quot;in&quot;, &quot;on&quot;, &quot;at&quot;, &quot;to&quot;, &quot;for&quot;, &quot;with&quot;, &quot;and&quot;, &quot;or&quot;, &quot;but&quot;, &quot;is&quot;, &quot;was&quot;, &quot;are&quot;, &quot;were&quot;, &quot;be&quot;, &quot;been&quot;, &quot;has&quot;, &quot;had&quot;, &quot;have&quot;, &quot;do&quot;, &quot;does&quot;, &quot;did&quot;, &quot;not&quot;, &quot;no&quot;, &quot;yes&quot;, &quot;it&quot;, &quot;its&quot;, &quot;from&quot;, &quot;by&quot;, &quot;as&quot;, &quot;up&quot;, &quot;down&quot;, &quot;out&quot;, &quot;into&quot;, &quot;through&quot;, &quot;over&quot;, &quot;under&quot;, &quot;about&quot;, &quot;before&quot;, &quot;after&quot;, &quot;then&quot;, &quot;now&quot;, &quot;here&quot;, &quot;there&quot;, &quot;when&quot;, &quot;where&quot;, &quot;why&quot;, &quot;how&quot;, &quot;what&quot;, &quot;which&quot;, &quot;who&quot;, &quot;whom&quot;, &quot;whose&quot;, &quot;this&quot;, &quot;that&quot;, &quot;these&quot;, &quot;those&quot;, &quot;my&quot;, &quot;your&quot;, &quot;his&quot;, &quot;her&quot;, &quot;its&quot;, &quot;our&quot;, &quot;their&quot;, &quot;me&quot;, &quot;you&quot;, &quot;him&quot;, &quot;she&quot;, &quot;he&quot;, &quot;we&quot;, &quot;they&quot;, &quot;us&quot;, &quot;them&quot;,
            &quot;one&quot;, &quot;two&quot;, &quot;three&quot;, &quot;four&quot;, &quot;five&quot;, &quot;six&quot;, &quot;seven&quot;, &quot;eight&quot;, &quot;nine&quot;, &quot;ten&quot;,
            &quot;i&quot;, &quot;ii&quot;, &quot;iii&quot;, &quot;iv&quot;, &quot;v&quot;, &quot;vi&quot;, &quot;vii&quot;, &quot;viii&quot;, &quot;ix&quot;, &quot;x&quot;, // Roman numerals
            &quot;chapter&quot;, &quot;season&quot;, &quot;episode&quot;, &quot;part&quot;, &quot;edition&quot;, &quot;remastered&quot;, &quot;redux&quot;, &quot;director&#39;s&quot;, &quot;cut&quot;, &quot;deluxe&quot;, &quot;complete&quot;, &quot;pack&quot;, &quot;collection&quot;, &quot;special&quot;, &quot;ultimate&quot;, &quot;gold&quot;, &quot;hd&quot;, &quot;remake&quot;, &quot;origins&quot;, &quot;adventure&quot;, &quot;series&quot;, &quot;story&quot;, &quot;tales&quot;, &quot;challenge&quot;, &quot;game&quot;, &quot;title&quot;, &quot;quest&quot;, &quot;chronicles&quot;, &quot;saga&quot;, &quot;legacy&quot;, &quot;curse&quot;, &quot;mystery&quot;, &quot;secret&quot;, &quot;world&quot;, &quot;dreams&quot;, &quot;dark&quot;, &quot;light&quot;, &quot;time&quot;, &quot;space&quot;, &quot;police&quot;, &quot;king&#39;s&quot;, &quot;monkey&quot;, &quot;broken&quot;, &quot;sword&quot;, &quot;tex&quot;, &quot;murphy&quot;, &quot;sarien&quot;, &quot;encounter&quot;, &quot;lechuck&#39;s&quot;, &quot;revenge&quot;, &quot;curse&quot;, &quot;escape&quot;, &quot;screaming&quot;, &quot;narwhal&quot;, &quot;siege&quot;, &quot;spinner&quot;, &quot;cay&quot;, &quot;lair&quot;, &quot;leviathan&quot;, &quot;trial&quot;, &quot;tribulations&quot;, &quot;guybrush&quot;, &quot;threepwood&quot;, &quot;rise&quot;, &quot;pirate&quot;, &quot;god&quot;, &quot;return&quot;, &quot;shadow&quot;, &quot;templars&quot;, &quot;smoking&quot;, &quot;mirror&quot;, &quot;sleeping&quot;, &quot;dragon&quot;, &quot;angel&quot;, &quot;death&quot;, &quot;serpent&#39;s&quot;, &quot;parzival&#39;s&quot;, &quot;stone&quot;, &quot;mean&quot;, &quot;streets&quot;, &quot;martian&quot;, &quot;memorandum&quot;, &quot;killing&quot;, &quot;moon&quot;, &quot;pandora&quot;, &quot;directive&quot;, &quot;overseer&quot;, &quot;tesla&quot;, &quot;effect&quot;, &quot;crown&quot;, &quot;romancing&quot;, &quot;throne&quot;, &quot;heir&quot;, &quot;human&quot;, &quot;perils&quot;, &quot;rosella&quot;, &quot;absence&quot;, &quot;heart&quot;, &quot;yonder&quot;, &quot;gone&quot;, &quot;tomorrow&quot;, &quot;princeless&quot;, &quot;bride&quot;, &quot;mask&quot;, &quot;eternity&quot;, &quot;vohaul&#39;s&quot;, &quot;pirates&quot;, &quot;pestulon&quot;, &quot;roger&quot;, &quot;wilco&quot;, &quot;rippers&quot;, &quot;mutation&quot;, &quot;spinal&quot;, &quot;frontier&quot;, &quot;pursuit&quot;, &quot;death&quot;, &quot;angel&quot;, &quot;vengeance&quot;, &quot;kindred&quot;, &quot;open&quot;, &quot;swat&quot;, &quot;ankh&quot;, &quot;osiris&quot;, &quot;battle&quot;, &quot;gods&quot;, &quot;paranormal&quot;, &quot;investigator&quot;, &quot;case&quot;, &quot;search&quot;, &quot;skunk-ape&quot;, &quot;lost&quot;, &quot;galleon&quot;, &quot;salton&quot;, &quot;sea&quot;, &quot;sorceress&quot;, &quot;smailholm&quot;, &quot;horror&quot;, &quot;number&quot;, &quot;land&quot;, &quot;rising&quot;, &quot;dead&quot;, &quot;scourge&quot;, &quot;sea&quot;, &quot;people&quot;, &quot;cardinal&quot;, &quot;sins&quot;, &quot;relics&quot;, &quot;past&quot;, &quot;shivah&quot;, &quot;unbound&quot;, &quot;emerald&quot;, &quot;city&quot;, &quot;confidential&quot;, &quot;puzzle&quot;, &quot;bots&quot;, &quot;gemini&quot;, &quot;rue&quot;, &quot;da&quot;, &quot;new&quot;, &quot;guys&quot;, &quot;resonance&quot;, &quot;golden&quot;, &quot;wake&quot;, &quot;epiphany&quot;, &quot;deception&quot;, &quot;convergence&quot;, &quot;legacy&quot;, &quot;primordia&quot;, &quot;shardlight&quot;, &quot;technobabylon&quot;, &quot;strangeland&quot;, &quot;excavation&quot;, &quot;hob&#39;s&quot;, &quot;barrow&quot;, &quot;unavowed&quot;, &quot;old&quot;, &quot;skies&quot;, &quot;nighthawks&quot;, &quot;captain&quot;, &quot;disaster&quot;, &quot;dark&quot;, &quot;side&quot;, &quot;moon&quot;, &quot;death&quot;, &quot;million&quot;, &quot;stomping&quot;, &quot;boots&quot;, &quot;two&quot;, &quot;worlds&quot;, &quot;riskara&quot;, &quot;dig&quot;, &quot;indiana&quot;, &quot;jones&quot;, &quot;last&quot;, &quot;crusade&quot;, &quot;fate&quot;, &quot;atlantis&quot;, &quot;infernal&quot;, &quot;machine&quot;, &quot;emperor&#39;s&quot;, &quot;tomb&quot;, &quot;zork&quot;, &quot;great&quot;, &quot;underground&quot;, &quot;empire&quot;, &quot;wizard&quot;, &quot;frobozz&quot;, &quot;dungeon&quot;, &quot;master&quot;, &quot;deadline&quot;, &quot;star&quot;, &quot;cross&quot;, &quot;suspended&quot;, &quot;11th Hour&quot;, &quot;planetfall&quot;, &quot;witness&quot;, &quot;enchanter&quot;, &quot;infidel&quot;, &quot;sorcerer&quot;, &quot;seastalker&quot;, &quot;cutthroats&quot;, &quot;hitchhiker&#39;s&quot;, &quot;guide&quot;, &quot;galaxy&quot;, &quot;mind&quot;, &quot;forever&quot;, &quot;voyaging&quot;, &quot;wishbringer&quot;, &quot;spellbreaker&quot;, &quot;ballyhoo&quot;, &quot;trinity&quot;, &quot;moonmist&quot;, &quot;hollywood&quot;, &quot;hijinx&quot;, &quot;leather&quot;, &quot;goddesses&quot;, &quot;phobos&quot;, &quot;border&quot;, &quot;zone&quot;, &quot;bureaucracy&quot;, &quot;stationfall&quot;, &quot;nord&quot;, &quot;bert&quot;, &quot;couldn&#39;t&quot;, &quot;make&quot;, &quot;head&quot;, &quot;tail&quot;, &quot;lurking&quot;, &quot;horror&quot;, &quot;plundered&quot;, &quot;hearts&quot;, &quot;beyond&quot;, &quot;sherlock&quot;, &quot;riddle&quot;, &quot;crown&quot;, &quot;jewels&quot;, &quot;zero&quot;, &quot;revenge&quot;, &quot;megaboz&quot;, &quot;journey&quot;, &quot;arthur&quot;, &quot;excalibur&quot;, &quot;colossal&quot;, &quot;adventure&quot;, &quot;quest&quot;, &quot;dungeon&quot;, &quot;emerald&quot;, &quot;isle&quot;, &quot;lords&quot;, &quot;red&quot;, &quot;price&quot;, &quot;magik&quot;, &quot;return&quot;, &quot;eden&quot;, &quot;snowball&quot;, &quot;worm&quot;, &quot;paradise&quot;, &quot;diary&quot;, &quot;adrian&quot;, &quot;mole&quot;, &quot;aged&quot;, &quot;archers&quot;, &quot;growing&quot;, &quot;pains&quot;, &quot;knight&quot;, &quot;orc&quot;, &quot;gnome&quot;, &quot;ranger&quot;, &quot;time&quot;, &quot;ingrid&#39;s&quot;, &quot;back&quot;, &quot;scapeghost&quot;, &quot;erik&quot;, &quot;viking&quot;, &quot;champion&quot;, &quot;raj&quot;, &quot;pawn&quot;, &quot;guild&quot;, &quot;thieves&quot;, &quot;jinxter&quot;, &quot;corruption&quot;, &quot;fish&quot;, &quot;myth&quot;, &quot;wonderland&quot;, &quot;legacy&quot;, &quot;realm&quot;, &quot;terror&quot;, &quot;thimbleweed&quot;, &quot;park&quot;, &quot;kathy&quot;, &quot;rain&quot;, &quot;darkside&quot;, &quot;detective&quot;, &quot;norco&quot;, &quot;golden&quot;, &quot;idol&quot;, &quot;obra&quot;, &quot;dinn&quot;, &quot;unforeseen&quot;, &quot;incidents&quot;, &quot;lucy&quot;, &quot;dreaming&quot;, &quot;blind&quot;, &quot;prophet&quot;, &quot;blood&quot;, &quot;nova&quot;, &quot;call&quot;, &quot;sea&quot;, &quot;chicken&quot;, &quot;police&quot;, &quot;paint&quot;, &quot;red&quot;, &quot;chuchel&quot;, &quot;contradiction&quot;, &quot;spot&quot;, &quot;liar&quot;, &quot;di&quot;, &quot;silk&quot;, &quot;rose&quot;, &quot;murders&quot;, &quot;gibbous&quot;, &quot;cthulhu&quot;, &quot;heaven&#39;s&quot;, &quot;vault&quot;, &quot;immortality&quot;, &quot;inspector&quot;, &quot;waffles&quot;, &quot;lacuna&quot;,
            &quot;the last door&quot;, &quot;leisure suit larry&quot;, &quot;nelly cootalot&quot;, &quot;neocab&quot;, &quot;pentiment&quot;, &quot;röki&quot;, &quot;saint kotar&quot;, &quot;she sees red&quot;, &quot;stasis&quot;, &quot;tangle tower&quot;, &quot;tsioque&quot;, &quot;voodoo detective&quot;, &quot;willy morgan&quot;, &quot;whispers of a machine&quot;, &quot;the journey down&quot;, &quot;milk&quot;, &quot;disco elysium&quot;, &quot;life is strange&quot;, &quot;the wolf among us&quot;, &quot;sam &amp; max&quot;, &quot;the walking dead&quot;, &quot;batman&quot;, &quot;the coma&quot;, &quot;detention&quot;, &quot;devotion&quot;, &quot;gris&quot;, &quot;what remains of edith finch&quot;, &quot;firewatch&quot;, &quot;oxenfree&quot;, &quot;night in the woods&quot;, &quot;mutropolis&quot;, &quot;the procession to cavalry&quot;, &quot;lamplight city&quot;, &quot;bear with me&quot;, &quot;truberbrook&quot;, &quot;detective gallo&quot;, &quot;the will of arthur flabbington&quot;, &quot;unusual findings&quot;, &quot;darkestville castle&quot;, &quot;loco motive&quot;, &quot;crimson diamond&quot;, &quot;slender threads&quot;, &quot;rosewater&quot;, &quot;elroy and the aliens&quot;, &quot;the flayed man&quot;, &quot;simon the sorcerer&quot;, &quot;the red strings club&quot;, &quot;virtuaverse&quot;, &quot;chinatown detective agency&quot;, &quot;state of mind&quot;, &quot;dex&quot;, &quot;shadowgate&quot;, &quot;obduction&quot;, &quot;cthulhu&quot;, &quot;dropsy&quot;, &quot;fran bow&quot;, &quot;little misfortune&quot;, &quot;samorost&quot;, &quot;creaks&quot;, &quot;creaks&quot;, &quot;pilgrims&quot;, &quot;deponia&quot;, &quot;silence&quot;, &quot;anna&#39;s quest&quot;, &quot;styx&quot;, &quot;the raven&quot;, &quot;black mirror&quot;, &quot;agatha christie&quot;, &quot;hercule poirot&quot;, &quot;alfred hitchcock&quot;, &quot;blacksad&quot;, &quot;yesterday origins&quot;, &quot;amerzone&quot;, &quot;centum&quot;, &quot;mutants ate my carrots&quot;, &quot;shadows of the afterland&quot;, &quot;the hand of glory&quot;, &quot;whirlight&quot;,
            // New additions to excluded words based on new titles and common series terms
            &quot;mansion&quot;, &quot;mckracken&quot;, &quot;alien&quot;, &quot;mindbenders&quot;, &quot;loom&quot;, &quot;tentacle&quot;, &quot;road&quot;, &quot;throttle&quot;, &quot;grim&quot;, &quot;fandango&quot;, &quot;gabriel&quot;, &quot;knight&quot;, &quot;sins&quot;, &quot;fathers&quot;, &quot;beast&quot;, &quot;within&quot;, &quot;blood&quot;, &quot;sacred&quot;, &quot;damned&quot;, &quot;myst&quot;, &quot;riven&quot;, &quot;sequel&quot;, &quot;exile&quot;, &quot;longest&quot;, &quot;journey&quot;, &quot;dreamfall&quot;, &quot;discworld&quot;, &quot;missing&quot;, &quot;presumed&quot;, &quot;lion&quot;, &quot;wardrobe&quot;, &quot;beneath&quot;, &quot;steel&quot;, &quot;sky&quot;, &quot;flight&quot;, &quot;amazon&quot;, &quot;queen&quot;, &quot;interactive&quot;, &quot;blade&quot;, &quot;runner&quot;, &quot;sanitarium&quot;, &quot;mouth&quot;, &quot;scream&quot;, &quot;guest&quot;, &quot;hour&quot;, &quot;phantasmagoria&quot;, &quot;puzzle&quot;, &quot;flesh&quot;, &quot;anniversary&quot;, &quot;pegasus&quot;, &quot;prime&quot;, &quot;buried&quot;, &quot;legacy&quot;, &quot;nemesis&quot;, &quot;forbidden&quot;, &quot;lands&quot;, &quot;grand&quot;, &quot;inquisitor&quot;, &quot;return&quot;, &quot;revelation&quot;, &quot;end&quot;, &quot;ages&quot;, &quot;definitive&quot;, &quot;shadows&quot;, &quot;cutting&quot;, &quot;class&quot;, &quot;devespresso&quot;, &quot;finji&quot;, &quot;pirita&quot;, &quot;studio&quot;, &quot;joe&quot;, &quot;richardson&quot;, &quot;modus&quot;, &quot;headup&quot;, &quot;productions&quot;, &quot;wormwood&quot;, &quot;epic&quot;, &quot;llama&quot;, &quot;buka&quot;, &quot;chucklefish&quot;, &quot;julia&quot;, &quot;minamata&quot;, &quot;bad&quot;, &quot;vertices&quot;, &quot;snoringdoggames&quot;, &quot;soft&quot;, &quot;devola&quot;, &quot;mastertronic&quot;, &quot;humble&quot;, &quot;daedalic&quot;, &quot;badland&quot;, &quot;reverb&quot;, &quot;triple&quot;, &quot;xp&quot;, &quot;cyan&quot;, &quot;worlds&quot;, &quot;focus&quot;, &quot;home&quot;, &quot;microids&quot;, &quot;double&quot;, &quot;fine&quot;, &quot;dropsy&quot;, &quot;fran&quot;, &quot;bow&quot;, &quot;killmonday&quot;, &quot;misfortune&quot;, &quot;amanita&quot;, &quot;creaks&quot;, &quot;pilgrims&quot;, &quot;deponia&quot;, &quot;silence&quot;, &quot;anna&#39;s&quot;, &quot;styx&quot;, &quot;shards&quot;, &quot;darkness&quot;, &quot;raven&quot;, &quot;thq&quot;, &quot;nordic&quot;, &quot;agatha&quot;, &quot;christie&quot;, &quot;abc&quot;, &quot;murders&quot;, &quot;hercule&quot;, &quot;poirot&quot;, &quot;alfred&quot;, &quot;hitchcock&quot;, &quot;vertigo&quot;, &quot;blacksad&quot;, &quot;skin&quot;, &quot;yesterday&quot;, &quot;amerzone&quot;, &quot;explorer&#39;s&quot;, &quot;centum&quot;, &quot;mutants&quot;, &quot;ate&quot;, &quot;carrots&quot;, &quot;afterland&quot;, &quot;hand&quot;, &quot;glory&quot;, &quot;roots&quot;, &quot;whirlight&quot;, &quot;trip&quot;,
            // Explicitly added common series words to exclude from being masked
            &quot;island&quot;, &quot;sword&quot;, &quot;murphy&quot;, &quot;jordan&quot;, &quot;quest&quot;, &quot;space&quot;, &quot;police&quot;, &quot;ankh&quot;, &quot;blackwell&quot;, &quot;captain&quot;, &quot;indiana&quot;, &quot;zork&quot;, &quot;syberia&quot;, &quot;discworld&quot;, &quot;simon&quot;, &quot;tex&quot;, &quot;myst&quot;, &quot;sam&quot;, &quot;max&quot;, &quot;batman&quot;, &quot;coma&quot;, &quot;detention&quot;, &quot;devotion&quot;, &quot;gris&quot;, &quot;firewatch&quot;, &quot;oxenfree&quot;, &quot;night in the woods&quot;, &quot;mutropolis&quot;, &quot;lamplight&quot;, &quot;bear&quot;, &quot;truberbrook&quot;, &quot;gallo&quot;, &quot;darkestville&quot;, &quot;loco&quot;, &quot;crimson&quot;, &quot;slender&quot;, &quot;rosewater&quot;, &quot;elroy&quot;, &quot;flayed&quot;, &quot;red&quot;, &quot;virtuaverse&quot;, &quot;chinatown&quot;, &quot;state&quot;, &quot;dex&quot;, &quot;shadowgate&quot;, &quot;obduction&quot;, &quot;cthulhu&quot;, &quot;dropsy&quot;, &quot;fran&quot;, &quot;misfortune&quot;, &quot;samorost&quot;, &quot;creaks&quot;, &quot;pilgrims&quot;, &quot;deponia&quot;, &quot;silence&quot;, &quot;anna&quot;, &quot;styx&quot;, &quot;raven&quot;, &quot;mirror&quot;, &quot;agatha&quot;, &quot;hercule&quot;, &quot;alfred&quot;, &quot;blacksad&quot;, &quot;yesterday&quot;, &quot;amerzone&quot;, &quot;centum&quot;, &quot;mutants&quot;, &quot;shadows&quot;, &quot;glory&quot;, &quot;whirlight&quot;,
            // Added based on user feedback
            &quot;ben&quot;, &quot;s&quot;
        ]);

        // Map to identify series and their common names for prepending
        const titleSeriesMap = {
            &quot;monkey island&quot;: &quot;Monkey Island&quot;,
            &quot;broken sword&quot;: &quot;Broken Sword&quot;,
            &quot;tex murphy&quot;: &quot;Tex Murphy&quot;,
            &quot;king&#39;s quest&quot;: &quot;King&#39;s Quest&quot;,
            &quot;space quest&quot;: &quot;Space Quest&quot;,
            &quot;police quest&quot;: &quot;Police Quest&quot;,
            &quot;ankh&quot;: &quot;Ankh&quot;,
            &quot;ben jordan&quot;: &quot;Ben Jordan: Paranormal Investigator&quot;,
            &quot;blackwell&quot;: &quot;The Blackwell&quot;,
            &quot;tales of monkey island&quot;: &quot;Tales of Monkey Island&quot;,
            &quot;captain disaster&quot;: &quot;Captain Disaster&quot;,
            &quot;indiana jones&quot;: &quot;Indiana Jones&quot;,
            &quot;zork&quot;: &quot;Zork&quot;,
            &quot;the last door&quot;: &quot;The Last Door&quot;,
            &quot;leisure suit larry&quot;: &quot;Leisure Suit Larry&quot;,
            &quot;the journey down&quot;: &quot;The Journey Down&quot;,
            &quot;milk&quot;: &quot;Milk&quot;,
            &quot;sam &amp; max&quot;: &quot;Sam &amp; Max&quot;,
            &quot;the walking dead&quot;: &quot;The Walking Dead&quot;,
            &quot;batman&quot;: &quot;Batman: The Telltale Series&quot;,
            &quot;the coma&quot;: &quot;The Coma&quot;,
            &quot;detention&quot;: &quot;Detention&quot;,
            &quot;devotion&quot;: &quot;Devotion&quot;,
            &quot;gris&quot;: &quot;Gris&quot;,
            &quot;what remains of edith finch&quot;: &quot;What Remains of Edith Finch&quot;,
            &quot;firewatch&quot;: &quot;Firewatch&quot;,
            &quot;oxenfree&quot;: &quot;Oxenfree&quot;,
            &quot;night in the woods&quot;: &quot;Night in the Woods&quot;,
            &quot;samorost&quot;: &quot;Samorost&quot;,
            &quot;creaks&quot;: &quot;Creaks&quot;,
            &quot;pilgrims&quot;: &quot;Pilgrims&quot;,
            &quot;deponia&quot;: &quot;Deponia&quot;,
            &quot;silence&quot;: &quot;Silence&quot;,
            &quot;anna&#39;s quest&quot;: &quot;Anna&#39;s Quest&quot;,
            &quot;styx&quot;: &quot;Styx&quot;,
            &quot;the raven&quot;: &quot;The Raven&quot;,
            &quot;black mirror&quot;: &quot;Black Mirror&quot;,
            &quot;agatha christie&quot;: &quot;Agatha Christie&quot;,
            &quot;hercule poirot&quot;: &quot;Hercule Poirot&quot;,
            &quot;alfred hitchcock&quot;: &quot;Alfred Hitchcock&quot;,
            &quot;blacksad&quot;: &quot;Blacksad&quot;,
            &quot;yesterday origins&quot;: &quot;Yesterday Origins&quot;,
            &quot;amerzone&quot;: &quot;Amerzone&quot;,
            &quot;centum&quot;: &quot;Centum&quot;,
            &quot;mutants ate my carrots&quot;: &quot;Mutants Ate My Carrots&quot;,
            &quot;shadows of the afterland&quot;: &quot;Shadows of the Afterland&quot;,
            &quot;the hand of glory&quot;: &quot;The Hand of Glory&quot;,
            &quot;whirlight&quot;: &quot;Whirlight&quot;,
            &quot;simon the sorcerer&quot;: &quot;Simon the Sorcerer&quot;,
            &quot;the red strings club&quot;: &quot;The Red Strings Club&quot;,
            &quot;virtuaverse&quot;: &quot;VirtuaVerse&quot;,
            &quot;chinatown detective agency&quot;: &quot;Chinatown Detective Agency&quot;,
            &quot;state of mind&quot;: &quot;State of Mind&quot;,
            &quot;dex&quot;: &quot;Dex&quot;,
            &quot;shadowgate&quot;: &quot;Shadowgate&quot;,
            &quot;obduction&quot;: &quot;Obduction&quot;,
            &quot;call of cthulhu&quot;: &quot;Call of Cthulhu&quot;,
            &quot;syberia&quot;: &quot;Syberia&quot;,
            &quot;broken age&quot;: &quot;Broken Age&quot;,
            &quot;dropsy&quot;: &quot;Dropsy&quot;,
            &quot;fran bow&quot;: &quot;Fran Bow&quot;,
            &quot;little misfortune&quot;: &quot;Little Misfortune&quot;,
            &quot;mutropolis&quot;: &quot;Mutropolis&quot;,
            &quot;the procession to cavalry&quot;: &quot;The Procession to Calvary&quot;,
            &quot;lamplight city&quot;: &quot;Lamplight City&quot;,
            &quot;bear with me&quot;: &quot;Bear With Me&quot;,
            &quot;truberbrook&quot;: &quot;Truberbrook&quot;,
            &quot;detective gallo&quot;: &quot;Detective Gallo&quot;,
            &quot;the will of arthur flabbington&quot;: &quot;The Will of Arthur Flabbington&quot;,
            &quot;unusual findings&quot;: &quot;Unusual Findings&quot;,
            &quot;darkestville castle&quot;: &quot;Darkestville Castle&quot;,
            &quot;loco motive&quot;: &quot;Loco Motive&quot;,
            &quot;crimson diamond&quot;: &quot;Crimson Diamond&quot;,
            &quot;slender threads&quot;: &quot;Slender Threads&quot;,
            &quot;rosewater&quot;: &quot;Rosewater&quot;,
            &quot;elroy and the aliens&quot;: &quot;Elroy and the Aliens&quot;,
            &quot;the flayed man&quot;: &quot;The Flayed Man&quot;,
            &quot;kathy rain&quot;: &quot;Kathy Rain&quot;,
            &quot;the darkside detective&quot;: &quot;The Darkside Detective&quot;,
            &quot;the case of the golden idol&quot;: &quot;The Case of the Golden Idol&quot;,
            &quot;the excavation of Hob&#39;s Barrow&quot;: &quot;The Excavation of Hob&#39;s Barrow&quot;,
            &quot;unavowed&quot;: &quot;Unavowed&quot;,
            &quot;old skies&quot;: &quot;Old Skies&quot;,
            &quot;technobabylon&quot;: &quot;Technobabylon&quot;,
            &quot;strangeland&quot;: &quot;Strangeland&quot;,
            &quot;primordia&quot;: &quot;Primordia&quot;,
            &quot;shardlight&quot;: &quot;Shardlight&quot;,
            &quot;gemini rue&quot;: &quot;Gemini Rue&quot;,
            &quot;resonance&quot;: &quot;Resonance&quot;,
            &quot;a golden wake&quot;: &quot;A Golden Wake&quot;,
            &quot;the shivah&quot;: &quot;The Shivah&quot;,
            &quot;emerald city confidential&quot;: &quot;Emerald City Confidential&quot;,
            &quot;puzzle bots&quot;: &quot;Puzzle Bots&quot;,
            &quot;da new guys&quot;: &quot;Da New Guys&quot;,
            &quot;whispers of a machine&quot;: &quot;Whispers of a Machine&quot;,
            &quot;willy morgan&quot;: &quot;Willy Morgan&quot;,
            &quot;gibbous&quot;: &quot;Gibbous&quot;,
            &quot;nelly cootalot&quot;: &quot;Nelly Cootalot&quot;,
            &quot;tsioque&quot;: &quot;Tsioque&quot;,
            &quot;voodoo detective&quot;: &quot;Voodoo Detective&quot;,
            &quot;maniac mansion&quot;: &quot;Maniac Mansion&quot;,
            &quot;zak mckracken&quot;: &quot;Zak McKracken&quot;,
            &quot;day of the tentacle&quot;: &quot;Day of the Tentacle&quot;,
            &quot;sam &amp; max hit the road&quot;: &quot;Sam &amp; Max&quot;,
            &quot;full throttle&quot;: &quot;Full Throttle&quot;,
            &quot;grim fandango&quot;: &quot;Grim Fandango&quot;,
            &quot;gabriel knight&quot;: &quot;Gabriel Knight&quot;,
            &quot;myst&quot;: &quot;Myst&quot;,
            &quot;riven&quot;: &quot;Riven&quot;,
            &quot;the longest journey&quot;: &quot;The Longest Journey&quot;,
            &quot;discworld&quot;: &quot;Discworld&quot;,
            &quot;beneath a steel sky&quot;: &quot;Beneath a Steel Sky&quot;,
            &quot;flight of the amazon queen&quot;: &quot;Flight of the Amazon Queen&quot;,
            &quot;blade runner&quot;: &quot;Blade Runner&quot;,
            &quot;sanitarium&quot;: &quot;Sanitarium&quot;,
            &quot;i have no mouth and i must scream&quot;: &quot;I Have No Mouth, and I Must Scream&quot;,
            &quot;the 7th guest&quot;: &quot;The 7th Guest&quot;,
            &quot;the 11th Hour&quot;: &quot;The 11th Hour&quot;,
            &quot;phantasmagoria&quot;: &quot;Phantasmagoria&quot;,
            &quot;the journeyman project&quot;: &quot;The Journeyman Project&quot;,
            &quot;the last express&quot;: &quot;The Last Express&quot;,
            &quot;zork nemesis&quot;: &quot;Zork Nemesis&quot;,
            &quot;zork grand inquisitor&quot;: &quot;Zork Grand Inquisitor&quot;,
            &quot;return to zork&quot;: &quot;Return to Zork&quot;,
            &quot;myst iv&quot;: &quot;Myst IV&quot;,
            &quot;myst v&quot;: &quot;Myst V&quot;
        };


        // --- Helper Functions ---

        /**
         * Shuffles an array in place using the Fisher-Yates (Knuth) shuffle algorithm.
         * @param {Array} array The array to shuffle.
         * @returns {Array} The shuffled array.
         */
        function shuffleArray(array) {
            for (let i = array.length - 1; i &gt; 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [array[i], array[j]] = [array[j], array[i]];
            }
            return array;
        }

        /**
         * Gets a random integer between min (inclusive) and max (inclusive).
         * @param {number} min The minimum value.
         * @param {number} max The maximum value.
         * @returns {number} A random integer.
         */
        function getRandomInt(min, max) {
            min = Math.ceil(min);
            max = Math.floor(max);
            return Math.floor(Math.random() * (max - min + 1)) + min;
        }

        /**
         * Converts a string to Title Case.
         * @param {string} str The string to convert.
         * @returns {string} The Title Cased string.
         */
        function toTitleCase(str) {
            return str.replace(/\w\S*/g, function(txt) {
                return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
            });
        }

        /**
         * Extracts words from a title, converts them to lowercase, and removes punctuation.
         * @param {string} title The game title.
         * @returns {Array&lt;string&gt;} An array of lowercase words.
         */
        function getWords(title) {
            // This regex matches sequences of word characters, effectively stripping punctuation
            return title.toLowerCase().match(/\b\w+\b/g) || [];
        }

        /**
         * Determines the series key for a given title.
         * @param {string} title The game title.
         * @returns {string|null} The series key or null if not part of a known series.
         */
        function getSeriesKey(title) {
            const lowerTitle = title.toLowerCase();
            for (const key in titleSeriesMap) {
                if (lowerTitle.includes(key)) {
                    return key;
                }
            }
            return null;
        }

        /**
         * Pre-processes the raw game titles:
         * - Removes duplicates based on title and year.
         * - Removes &quot;(GOG)&quot; from titles.
         * - Adds a &#39;series&#39; property to each game object.
         * @param {Array&lt;Object&gt;} titles The raw game titles.
         * @returns {Array&lt;Object&gt;} The processed game titles.
         */
        function preprocessGameTitles(titles) {
            const uniqueTitles = new Map(); // Map to store unique titles based on title+year
            const seriesCounts = {}; // To count how many games belong to each series

            titles.forEach(game =&gt; {
                // Remove &quot;(GOG)&quot; from the title
                game.title = game.title.replace(/\s*\(GOG\)\s*/gi, &#39;&#39;).trim();

                const key = `${game.title}-${game.year}`; // Unique key for filtering duplicates
                if (!uniqueTitles.has(key)) {
                    const seriesKey = getSeriesKey(game.title);
                    game.series = seriesKey; // Add series property
                    uniqueTitles.set(key, game);

                    if (seriesKey) {
                        seriesCounts[seriesKey] = (seriesCounts[seriesKey] || 0) + 1;
                    }
                }
            });

            // Filter out games that don&#39;t have enough words to be masked meaningfully
            const filteredTitles = Array.from(uniqueTitles.values()).filter(game =&gt; {
                const words = getWords(game.title); // getWords already returns lowercase, no punctuation
                const maskableWords = words.filter(word =&gt; !excludedWords.has(word));
                // Ensure there&#39;s at least one maskable word and it&#39;s not a single-word title
                // if that single word is the one being masked.
                return maskableWords.length &gt; 0 &amp;&amp; (words.length &gt; 1 || !excludedWords.has(words[0]));
            });

            // Add isLargeSeries property
            filteredTitles.forEach(game =&gt; {
                if (game.series) {
                    game.isLargeSeries = seriesCounts[game.series] &gt;= largeSeriesThreshold;
                } else {
                    game.isLargeSeries = false;
                }
            });

            return filteredTitles;
        }

        /**
         * Checks if masking a specific word in a title would create an ambiguous question.
         * An ambiguous question means another title in the gameTitles list could produce
         * the same masked string by masking a different word.
         * @param {Object} currentTitleObj The current game title object.
         * @param {string} maskedWord The word chosen to be masked (lowercase, no punctuation).
         * @param {Array&lt;Object&gt;} allTitles The complete list of game title objects.
         * @returns {boolean} True if the question is ambiguous, false otherwise.
         */
        function isAmbiguous(currentTitleObj, maskedWord, allTitles) {
            // Normalize current title words for comparison
            const currentTitleWordsNormalized = currentTitleObj.title.split(/\s+/).map(word =&gt; word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase());

            // Create the masked string for the current title using the chosen maskedWord
            const currentMaskedTitleString = currentTitleWordsNormalized.map(word =&gt;
                word === maskedWord ? &#39;[____]&#39; : word
            ).join(&#39; &#39;);

            for (const otherTitleObj of allTitles) {
                // Skip if it&#39;s the same title object
                if (otherTitleObj === currentTitleObj) continue;

                // Normalize other title words for comparison
                const otherTitleWordsNormalized = otherTitleObj.title.split(/\s+/).map(word =&gt; word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase());

                for (let i = 0; i &lt; otherTitleWordsNormalized.length; i++) {
                    const otherWord = otherTitleWordsNormalized[i];
                    // Check if masking &#39;otherWord&#39; in &#39;otherTitleObj&#39; results in the same masked string
                    // AND &#39;otherWord&#39; is not the same as &#39;maskedWord&#39; AND &#39;otherWord&#39; is not excluded
                    if (otherWord !== maskedWord &amp;&amp; !excludedWords.has(otherWord) &amp;&amp; otherWord.length &gt; 1) {
                        let tempOtherTitleWords = [...otherTitleWordsNormalized];
                        tempOtherTitleWords[i] = &#39;[____]&#39;;
                        const potentialMaskedOtherTitle = tempOtherTitleWords.join(&#39; &#39;);

                        if (potentialMaskedOtherTitle === currentMaskedTitleString) {
                            return true; // Ambiguous found
                        }
                    }
                }
            }
            return false; // Not ambiguous
        }


        /**
         * Gets a list of unique incorrect words for options.
         * @param {string} correctWord The correct word (lowercase, no punctuation).
         * @param {string} currentTitle The current game title (original case, may have punctuation).
         * @param {Array&lt;Object&gt;} allTitles The complete list of game title objects.
         * @returns {Array&lt;string&gt;} An array of three unique incorrect words (original casing).
         */
        function getUniqueIncorrectWords(correctWord, currentTitle, allTitles) {
            const incorrectWordsSet = new Set();
            const currentTitleWordsLower = new Set(getWords(currentTitle)); // Words in current title (lowercase, no punctuation)
            const allPossibleMaskableWords = new Set(); // Stores original case words that are maskable

            // Collect all maskable words from all titles, preserving original casing
            allTitles.forEach(game =&gt; {
                game.title.split(/\s+/).forEach(wordWithPunctuation =&gt; {
                    const cleanLowerWord = wordWithPunctuation.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase();
                    if (!excludedWords.has(cleanLowerWord) &amp;&amp; cleanLowerWord.length &gt; 1) {
                        // Add the original word (with its original casing and punctuation)
                        allPossibleMaskableWords.add(wordWithPunctuation);
                    }
                });
            });

            const possibleIncorrects = Array.from(allPossibleMaskableWords).filter(word =&gt;
                word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase() !== correctWord.toLowerCase() &amp;&amp; // Not the correct word (normalized)
                !currentTitleWordsLower.has(word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase()) // Not in the current title (normalized)
            );

            shuffleArray(possibleIncorrects);

            // Take up to 3 unique incorrect words
            for (let i = 0; incorrectWordsSet.size &lt; 3 &amp;&amp; i &lt; possibleIncorrects.length; i++) {
                incorrectWordsSet.add(possibleIncorrects[i]);
            }

            // Fallback if not enough unique incorrect words are found (highly unlikely with this dataset)
            while (incorrectWordsSet.size &lt; 3) {
                incorrectWordsSet.add(`Option ${incorrectWordsSet.size + 1}`);
            }

            return Array.from(incorrectWordsSet);
        }

        /**
         * Updates the score display.
         */
        function updateScoreDisplay() {
            scoreDisplay.textContent = `Score: ${score}/${questionsAnswered}`;
        }

        /**
         * Clears the feedback message.
         */
        function clearFeedback() {
            feedbackMessageElement.textContent = &#39;&#39;;
            feedbackMessageElement.className = &#39;feedback-message&#39;; // Reset classes
        }

        /**
         * Disables all option buttons.
         */
        function disableButtons() {
            optionsGrid.querySelectorAll(&#39;.option-button&#39;).forEach(button =&gt; {
                button.disabled = true;
            });
        }

        /**
         * Enables all option buttons and removes highlight classes.
         */
        function enableButtons() {
            optionsGrid.querySelectorAll(&#39;.option-button&#39;).forEach(button =&gt; {
                button.disabled = false;
                button.classList.remove(&#39;correct&#39;, &#39;incorrect&#39;);
            });
        }

        /**
         * Decodes HTML entities in a string.
         * @param {string} text The string possibly containing HTML entities.
         * @returns {string} The string with HTML entities decoded.
         */
        function decodeHtmlEntities(text) {
            const textarea = document.createElement(&#39;textarea&#39;);
            textarea.innerHTML = text;
            return textarea.value;
        }


        // --- Game State Variables ---
        const totalQuestions = 20;
        const recentSeriesLimit = 10;
        const largeSeriesThreshold = 5;

        let gameTitles = []; // Shuffled and pre-processed list for the current game
        let currentAvailableTitles = []; // Titles available for the current game session
        let currentQuestionIndex = 0;
        let score = 0;
        let questionsAnswered = 0;
        let usedTitles = new Set(); // To prevent immediate repetition within a game session
        const recentSeriesQueue = []; // Stores last 10 series keys


        // --- DOM Elements ---
        const scoreDisplay = document.getElementById(&#39;score-display&#39;);
        const maskedTitleElement = document.getElementById(&#39;masked-title&#39;);
        const gameInfoElement = document.getElementById(&#39;game-info&#39;);
        const optionsGrid = document.getElementById(&#39;options-grid&#39;);
        const feedbackMessageElement = document.getElementById(&#39;feedback-message&#39;);
        const nextButton = document.getElementById(&#39;next-button&#39;);
        const shareButtonsContainer = document.getElementById(&#39;share-buttons&#39;);
        const shareTwitterButton = document.getElementById(&#39;share-twitter&#39;);
        const shareBlueskyButton = document.getElementById(&#39;share-bluesky&#39;);
        const shareMastodonButton = document.getElementById(&#39;share-mastodon&#39;);


        /**
         * Initializes the game state and starts the first question.
         */
        function startGame() {
            score = 0;
            questionsAnswered = 0;
            usedTitles.clear();
            recentSeriesQueue.length = 0; // Clear the queue
            gameTitles = shuffleArray(preprocessGameTitles(rawGameTitles)); // Re-shuffle and preprocess for new game
            currentAvailableTitles = [...gameTitles]; // Create a mutable copy for the current game session
            nextButton.textContent = &#39;Next Question&#39;;
            nextButton.disabled = true;
            updateScoreDisplay();
            clearFeedback();
            generateQuestion(); // Calls generateQuestion to set up the first question
        }

        /**
         * Generates a new question, including masking a word, preparing options, and updating the UI.
         */
        function generateQuestion() {
            if (questionsAnswered &gt;= totalQuestions) {
                endGame();
                return;
            }

            // Clear previous question&#39;s elements
            optionsGrid.innerHTML = &#39;&#39;;
            clearFeedback();
            nextButton.disabled = true;
            enableButtons(); // Re-enable buttons for the new question

            let selectedGame = null;
            let maskedWordCleanLower = null; // Cleaned, lowercase version of the masked word for comparison
            let maskedWordOriginalCase = &#39;&#39;; // Original case of the masked word (with punctuation if it had it)

            let foundQuestion = false;
            let attempts = 0;
            const maxGameSelectionAttempts = currentAvailableTitles.length * 2; // Limit attempts to find a suitable game

            // Loop to find a suitable game and a non-ambiguous word to mask
            while (!foundQuestion &amp;&amp; attempts &lt; maxGameSelectionAttempts &amp;&amp; currentAvailableTitles.length &gt; 0) {
                // Pick a random index from the currentAvailableTitles
                const randomIndex = getRandomInt(0, currentAvailableTitles.length - 1);
                const potentialGame = currentAvailableTitles[randomIndex];

                // Remove the potential game from the available pool for this attempt to avoid re-picking immediately
                // If it&#39;s suitable, it will be added to usedTitles. If not, it&#39;s discarded for this round.
                currentAvailableTitles.splice(randomIndex, 1);

                // Check for immediate repetition or recent large series repetition
                if (usedTitles.has(potentialGame.title) || (potentialGame.isLargeSeries &amp;&amp; recentSeriesQueue.includes(potentialGame.series))) {
                    attempts++; // Count this as an attempt, but don&#39;t re-add to pool
                    continue;
                }

                // Split title by spaces to get words, preserving original casing and punctuation for now
                const titleWordsWithPunctuation = potentialGame.title.split(/\s+/);
                let maskableWordsCandidates = [];

                // Identify all words that can be masked in this potential game
                for (let i = 0; i &lt; titleWordsWithPunctuation.length; i++) {
                    const word = titleWordsWithPunctuation[i];
                    const cleanLowerWord = word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase(); // Clean and lowercase for exclusion check
                    if (!excludedWords.has(cleanLowerWord) &amp;&amp; cleanLowerWord.length &gt; 1) { // Ensure word is not excluded and is longer than 1 character
                        maskableWordsCandidates.push({
                            originalWord: word, // Store original word with punctuation/casing
                            cleanLower: cleanLowerWord, // Store clean lowercase for checks
                            originalIndex: i
                        });
                    }
                }

                if (maskableWordsCandidates.length === 0) {
                    // console.warn(`No maskable words found for: &quot;${potentialGame.title}&quot;. Skipping.`);
                    attempts++;
                    continue; // Try next game
                }

                shuffleArray(maskableWordsCandidates); // Shuffle to try different words first

                let foundNonAmbiguousWordForThisGame = false;
                for (let i = 0; i &lt; maskableWordsCandidates.length; i++) {
                    const chosenCandidate = maskableWordsCandidates[i];
                    
                    // Check for ambiguity using the clean, lowercase version of the word
                    if (!isAmbiguous(potentialGame, chosenCandidate.cleanLower, gameTitles)) {
                        selectedGame = potentialGame;
                        maskedWordCleanLower = chosenCandidate.cleanLower;
                        maskedWordOriginalCase = chosenCandidate.originalWord; // Keep original for button text
                        foundNonAmbiguousWordForThisGame = true;
                        foundQuestion = true; // Found a complete valid question
                        break; // Found a valid word for this game, break inner loop
                    } else {
                        // console.log(`Ambiguous masking for &quot;${chosenCandidate.originalWord}&quot; in &quot;${potentialGame.title}&quot;. Trying another word for this title.`);
                    }
                }

                if (foundNonAmbiguousWordForThisGame) {
                    usedTitles.add(selectedGame.title); // Mark the selected game as used

                    // Add series to recent queue and manage its size
                    if (selectedGame.series) {
                        recentSeriesQueue.push(selectedGame.series);
                        if (recentSeriesQueue.length &gt; recentSeriesLimit) {
                            recentSeriesQueue.shift(); // Remove oldest entry
                        }
                    }
                } else {
                    // If no non-ambiguous word was found for the current potentialGame, it&#39;s effectively skipped.
                    // It was already removed from currentAvailableTitles at the beginning of this loop iteration.
                    // console.warn(`No non-ambiguous word found for &quot;${potentialGame.title}&quot;. Skipping.`);
                }
                attempts++;
            }

            // If after all attempts, no suitable question could be generated
            if (!foundQuestion) {
                console.error(&quot;Could not generate a suitable question after exhausting all attempts and titles.&quot;);
                maskedTitleElement.textContent = &quot;No more unique questions available!&quot;;
                gameInfoElement.textContent = &quot;Please refresh to play again.&quot;;
                optionsGrid.innerHTML = &#39;&#39;;
                feedbackMessageElement.textContent = &quot;Game over due to insufficient unique questions.&quot;;
                feedbackMessageElement.classList.add(&#39;text-red-400&#39;);
                nextButton.disabled = true; // Disable next button permanently
                return;
            }

            // Prepend series name if applicable and not already in title
            let displayTitle = selectedGame.title; // Start with the original title
            if (selectedGame.series) {
                const seriesName = titleSeriesMap[selectedGame.series];
                const lowerCaseSeriesName = seriesName.toLowerCase();
                const lowerCaseTitle = selectedGame.title.toLowerCase();

                // Check if the series name or a significant part of it is already in the title
                let seriesAlreadyInTitle = false;
                const seriesWords = lowerCaseSeriesName.split(&#39; &#39;);
                if (seriesWords.length &gt; 1) {
                    if (lowerCaseTitle.includes(`${seriesWords[0]} ${seriesWords[1]}`)) {
                        seriesAlreadyInTitle = true;
                    }
                } else {
                    if (lowerCaseTitle.includes(lowerCaseSeriesName)) {
                        seriesAlreadyInTitle = true;
                    }
                }

                if (!seriesAlreadyInTitle) {
                    displayTitle = `${seriesName}: ${selectedGame.title}`;
                }
            }

            // Now, apply the masking and Title Case to the displayTitle
            const displayWords = displayTitle.split(/\s+/);
            const finalMaskedDisplayTitle = displayWords.map(word =&gt; {
                const cleanLowerWord = word.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase();
                if (cleanLowerWord === maskedWordCleanLower) {
                    // This is the word to be masked. All occurrences will be masked.
                    return &#39;&lt;span class=&quot;text-yellow-400&quot;&gt;[____]&lt;/span&gt;&#39;;
                } else if (!excludedWords.has(cleanLowerWord)) {
                    // Apply Title Case to non-excluded, non-masked words
                    return toTitleCase(word);
                }
                return word; // Keep excluded words and punctuation as is (or their original casing)
            }).join(&#39; &#39;);

            maskedTitleElement.innerHTML = finalMaskedDisplayTitle;
            gameInfoElement.textContent = `Year: ${selectedGame.year} | Publisher: ${selectedGame.publisher || &#39;N/A&#39;}`;

            // Generate options
            // The correct option should be the original case of the masked word
            const correctOption = maskedWordOriginalCase;
            // Get incorrect options based on the clean, lowercase masked word
            const incorrectOptions = getUniqueIncorrectWords(maskedWordCleanLower, selectedGame.title, gameTitles);

            const allOptions = shuffleArray([correctOption, ...incorrectOptions.slice(0, 3)]); // Ensure exactly 4 options

            optionsGrid.innerHTML = &#39;&#39;; // Clear options again just in case
            allOptions.forEach(option =&gt; {
                const button = document.createElement(&#39;button&#39;);
                button.classList.add(&#39;option-button&#39;);
                // Use decodeHtmlEntities to ensure characters like &#39;¾&#39; display correctly
                button.textContent = decodeHtmlEntities(option);
                // Pass the clean, lowercase version of the correct word for checking
                button.addEventListener(&#39;click&#39;, () =&gt; checkAnswer(option, maskedWordOriginalCase, button));
                optionsGrid.appendChild(button);
            });
        }

        /**
         * Checks the user&#39;s answer, provides feedback, and updates the score.
         * @param {string} selectedWord The word selected by the user (original casing).
         * @param {string} correctWord The correct word for the question (original casing).
         * @param {HTMLElement} clickedButton The button element that was clicked.
         */
        function checkAnswer(selectedWord, correctWord, clickedButton) {
            disableButtons(); // Disable all buttons immediately

            const allOptionButtons = optionsGrid.querySelectorAll(&#39;.option-button&#39;);
            let isCorrect = false;

            // Decode correctWord before comparison to handle HTML entities
            const decodedCorrectWord = decodeHtmlEntities(correctWord);

            // Normalize selected and decoded correct words for comparison, ignoring punctuation and case
            const selectedWordCleanLower = selectedWord.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase();
            const correctWordCleanLower = decodedCorrectWord.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase();

            // Highlight correct answer
            allOptionButtons.forEach(button =&gt; {
                if (button.textContent.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase() === correctWordCleanLower) {
                    button.classList.add(&#39;correct&#39;);
                    if (button.textContent.replace(/[.,!?;:]/g, &#39;&#39;).toLowerCase() === selectedWordCleanLower) {
                        isCorrect = true;
                    }
                }
            });

            // Highlight incorrect clicked answer
            if (!isCorrect) {
                clickedButton.classList.add(&#39;incorrect&#39;);
                // Use the decodedCorrectWord for the feedback message
                feedbackMessageElement.textContent = `Incorrect. The correct word was &#39;${decodedCorrectWord}&#39;.`;
                feedbackMessageElement.classList.add(&#39;text-red-400&#39;);
            } else {
                score++;
                feedbackMessageElement.textContent = &#39;Correct! Well done!&#39;;
                feedbackMessageElement.classList.add(&#39;text-green-400&#39;);
            }

            questionsAnswered++;
            updateScoreDisplay();
            nextButton.disabled = false; // Enable next question button
            nextButton.focus(); // Focus the next button for accessibility
        }

        /**
         * Ends the game, displays final score, and changes button to &quot;Play Again&quot;.
         */
        function endGame() {
            console.log(&quot;endGame() called.&quot;);
            maskedTitleElement.textContent = &#39;Game Over!&#39;;
            gameInfoElement.textContent = `Final Score: ${score}/${totalQuestions}`;
            optionsGrid.innerHTML = &#39;&#39;; // Clear options

            // Dynamic feedback message based on score
            if (score === totalQuestions) {
                feedbackMessageElement.textContent = `Amazing! You got a perfect score of ${score} out of ${totalQuestions}!`;
                feedbackMessageElement.classList.remove(&#39;text-red-400&#39;);
                feedbackMessageElement.classList.add(&#39;text-green-400&#39;);
            } else if (score &gt;= totalQuestions / 2) {
                feedbackMessageElement.textContent = `Great effort! You scored ${score} out of ${totalQuestions}.`;
                feedbackMessageElement.classList.remove(&#39;text-red-400&#39;);
                feedbackMessageElement.classList.add(&#39;text-blue-400&#39;);
            } else {
                feedbackMessageElement.textContent = `Keep practicing! You scored ${score} out of ${totalQuestions}.`;
                feedbackMessageElement.classList.remove(&#39;text-green-400&#39;);
                feedbackMessageElement.classList.add(&#39;text-red-400&#39;);
            }
            
            nextButton.textContent = &#39;Play Again&#39;;
            nextButton.disabled = false;
            nextButton.removeEventListener(&#39;click&#39;, nextQuestion); // Remove old listener
            nextButton.addEventListener(&#39;click&#39;, startGame); // Add new listener for play again
        }

        /**
         * Sets up event listeners for the social media share buttons.
         * This function is called once on page load to attach persistent click handlers.
         * The share text is generated dynamically when a button is clicked.
         */
        function setupShareButtons() {
            const pageUrl = &quot;http://www.captaindisasterthecomputergame.com/2025/07/Adventure-Game-Missing-Word-Quiz.html&quot;; // Updated URL

            // Function to get the dynamic share message content (WITHOUT the URL)
            const getDynamicShareMessage = () =&gt; {
                if (questionsAnswered === totalQuestions &amp;&amp; score === totalQuestions) {
                    return `I scored a perfect ${score}/${totalQuestions} in the Adventure Game Title Challenge! Can you match it?`;
                }
                return `Check out this Adventure Game Title Challenge!`;
            };

            // Twitter/X Share
            shareTwitterButton.onclick = () =&gt; {
                const message = encodeURIComponent(getDynamicShareMessage());
                const url = encodeURIComponent(pageUrl);
                // Twitter&#39;s intent handles &#39;text&#39; and &#39;url&#39; separately for rich link previews
                window.open(`https://twitter.com/intent/tweet?text=${message}&amp;url=${url}&amp;hashtags=AdventureGame,GameQuiz`, &#39;_blank&#39;);
            };

            // Bluesky Share
            shareBlueskyButton.onclick = () =&gt; {
                const message = getDynamicShareMessage();
                const url = pageUrl;
                // For Bluesky, combine message and URL into the &#39;text&#39; parameter for reliable display
                const fullText = `${message} Play here: ${url}`;
                window.open(`https://bsky.app/intent/compose?text=${encodeURIComponent(fullText)}&amp;hashtags=AdventureGame,GameQuiz`, &#39;_blank&#39;);
            };

            // Mastodon Share
            shareMastodonButton.onclick = () =&gt; {
                const message = getDynamicShareMessage();
                const url = pageUrl;
                // For Mastodon, also combine message and URL into the &#39;text&#39; parameter.
                // The login issue is likely external to the code, but this ensures content is correct.
                const fullText = `${message} Play here: ${url}`;
                window.open(`https://mastodon.social/share?text=${encodeURIComponent(fullText)}&amp;hashtags=AdventureGame,GameQuiz`, &#39;_blank&#39;);
            };
        }


        // --- Event Listeners ---
        // The event listener for nextButton is set here initially.
        // It&#39;s removed and re-added in endGame() and startGame() to switch between &#39;Next Question&#39; and &#39;Play Again&#39;
        nextButton.addEventListener(&#39;click&#39;, nextQuestion);

        /**
         * Proceeds to the next question or ends the game.
         */
        function nextQuestion() {
            if (questionsAnswered &lt; totalQuestions) {
                // This is where the game progresses to the next question
                generateQuestion();
            } else {
                endGame();
            }
        }

        // --- Start the game when the page loads ---
        window.onload = () =&gt; {
            startGame();
            setupShareButtons(); // Attach event listeners once on load
        };

    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/developer-notes-for-adventure-game.html&quot;&gt;Developer notes for this game.&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/344187867678066412/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/Adventure-Game-Missing-Word-Quiz.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/344187867678066412'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/344187867678066412'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/Adventure-Game-Missing-Word-Quiz.html' title='Adventure Game Missing Word Quiz'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-2571338149008057618</id><published>2025-07-23T10:35:00.000-07:00</published><updated>2025-07-23T10:35:35.857-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="demo"/><category scheme="http://www.blogger.com/atom/ns#" term="itch"/><category scheme="http://www.blogger.com/atom/ns#" term="milestones"/><category scheme="http://www.blogger.com/atom/ns#" term="stats"/><category scheme="http://www.blogger.com/atom/ns#" term="steam"/><category scheme="http://www.blogger.com/atom/ns#" term="two worlds of riskara"/><category scheme="http://www.blogger.com/atom/ns#" term="voice acting"/><title type='text'>Captain Disaster and the Two Worlds of Riskara - three milestones acheived</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I&#39;m a big fan of milestones - without them, you can often feel like your projects are going nowhere (and you can apply this to various domains of life, not just specific projects). Er... so, anyway, 3 milestones we&#39;ve reached for the game recently:&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;- 150 demo downloads on &lt;b&gt;&lt;a href=&quot;https://captaind.itch.io/captain-disaster-and-the-two-worlds-of-riskara&quot; target=&quot;_blank&quot;&gt;Itch&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;- 400 demo downloads on &lt;b&gt;&lt;a href=&quot;https://store.steampowered.com/app/2242270/Captain_Disaster_and_The_Two_Worlds_of_Riskara/&quot; target=&quot;_blank&quot;&gt;Steam&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;- 1,000 voice lines both recorded and processed for Captain Disaster (who, as you may image, has far more voice lines in the game than any other character!) For reference, this is slightly over half his voice lines for the game. For all characters, a bit more than half have been recorded and processed.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;We&#39;re getting there. It feels painfully slow at times, but we &lt;b&gt;are &lt;/b&gt;getting there!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgAgY3-ex5HoHgS6ftMlThSbM6AGoYVEctPw8vBxKT8zydMNt5sh6oWLTlMUyRqB6EKEeu6QPW7EWZfeKGVTpN_-TiDaS-LBUh0GCKQU4A37AorEs9s1WwiwjLGozURc4Ctp6gK2QPN2qpfC_JI5cYGhLr4C215BvBzkYX7kflHK5GtounK4MPba9KbDjQ&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;1080&quot; data-original-width=&quot;1920&quot; height=&quot;360&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEgAgY3-ex5HoHgS6ftMlThSbM6AGoYVEctPw8vBxKT8zydMNt5sh6oWLTlMUyRqB6EKEeu6QPW7EWZfeKGVTpN_-TiDaS-LBUh0GCKQU4A37AorEs9s1WwiwjLGozURc4Ctp6gK2QPN2qpfC_JI5cYGhLr4C215BvBzkYX7kflHK5GtounK4MPba9KbDjQ=w640-h360&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/2571338149008057618/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/captain-disaster-and-two-worlds-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/2571338149008057618'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/2571338149008057618'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/captain-disaster-and-two-worlds-of.html' title='Captain Disaster and the Two Worlds of Riskara - three milestones acheived'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/a/AVvXsEgAgY3-ex5HoHgS6ftMlThSbM6AGoYVEctPw8vBxKT8zydMNt5sh6oWLTlMUyRqB6EKEeu6QPW7EWZfeKGVTpN_-TiDaS-LBUh0GCKQU4A37AorEs9s1WwiwjLGozURc4Ctp6gK2QPN2qpfC_JI5cYGhLr4C215BvBzkYX7kflHK5GtounK4MPba9KbDjQ=s72-w640-h360-c" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-2087161475109470593</id><published>2025-07-23T09:19:00.000-07:00</published><updated>2025-07-23T09:19:23.687-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ai hallucinations"/><category scheme="http://www.blogger.com/atom/ns#" term="ai search"/><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="fake adverts"/><category scheme="http://www.blogger.com/atom/ns#" term="hair styling"/><title type='text'>A Mirthful Tale of AI Hallucination!</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&amp;nbsp;A while ago we did a series of fake adverts for the game - like this one:&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipPVL8v_vhKxHKrdwa8ATwg8Sm-iw3huVQ-KJwK4bWLS9Vwc8kFgS8aktlIOdWeIQNO9UzcEDY2fgd0rBMHkDPJHnGvLQ4APSWZW61HRX-HXqaAFYtw7E9fhEwvdllbuoavBcPC52dEm4sNuVOVWGJaNXIaeU8g0xRUm5KHcA9484vUwiXpH4KnyU8FlQ/s1600/hair-poster.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1600&quot; data-original-width=&quot;1400&quot; height=&quot;640&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipPVL8v_vhKxHKrdwa8ATwg8Sm-iw3huVQ-KJwK4bWLS9Vwc8kFgS8aktlIOdWeIQNO9UzcEDY2fgd0rBMHkDPJHnGvLQ4APSWZW61HRX-HXqaAFYtw7E9fhEwvdllbuoavBcPC52dEm4sNuVOVWGJaNXIaeU8g0xRUm5KHcA9484vUwiXpH4KnyU8FlQ/w560-h640/hair-poster.png&quot; width=&quot;560&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;Now, whilst I tend to obsessively save everything (and indeed, I had a copy of this file if only I&#39;d remembered where I put it!), my mind had gone as blank as the Captain&#39;s for a moment and I decided to see if I could find it by searching the web. So I searched for &quot;Captain Disaster how does he get his hair so straight&quot;.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;As you may know, Google&#39;s search (and no doubt several others) has a public AI component to it now. Whilst the image itself did not want to be found, AI hallucinated this lovely method of CD keeping his hair straight.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;If you actually try this method, please let me know!! :-D&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiks_TxTHsSqCE0F9M9vi0AdTuB9116nxDWqKFl2nyaySLwHR4MykcOmXrRpEpvJ8noDVbusUszRb2o7RvEAUCCu3G0AiT5zKdZomBTjvEp4udyKnYGNPPECFRuq_kUIw2n17b9sbvpcjcmRfp4tSSaEzmq1tlec_YFmnavl_2Buot8hJ9VRuKk1xHHxoA&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img alt=&quot;&quot; data-original-height=&quot;896&quot; data-original-width=&quot;783&quot; src=&quot;https://blogger.googleusercontent.com/img/a/AVvXsEiks_TxTHsSqCE0F9M9vi0AdTuB9116nxDWqKFl2nyaySLwHR4MykcOmXrRpEpvJ8noDVbusUszRb2o7RvEAUCCu3G0AiT5zKdZomBTjvEp4udyKnYGNPPECFRuq_kUIw2n17b9sbvpcjcmRfp4tSSaEzmq1tlec_YFmnavl_2Buot8hJ9VRuKk1xHHxoA=s16000&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;br /&gt;&lt;div&gt;Also, there was no mention of alien slime secretions, which is frankly unforgiveable.&lt;p&gt;&lt;/p&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/2087161475109470593/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/a-mirthful-tale-of-ai-hallucination.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/2087161475109470593'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/2087161475109470593'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/a-mirthful-tale-of-ai-hallucination.html' title='A Mirthful Tale of AI Hallucination!'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEipPVL8v_vhKxHKrdwa8ATwg8Sm-iw3huVQ-KJwK4bWLS9Vwc8kFgS8aktlIOdWeIQNO9UzcEDY2fgd0rBMHkDPJHnGvLQ4APSWZW61HRX-HXqaAFYtw7E9fhEwvdllbuoavBcPC52dEm4sNuVOVWGJaNXIaeU8g0xRUm5KHcA9484vUwiXpH4KnyU8FlQ/s72-w560-h640-c/hair-poster.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-4464778548012055505</id><published>2025-07-23T02:36:00.000-07:00</published><updated>2025-07-23T03:25:08.757-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="chat gpt"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="secure password generator"/><title type='text'>Secure Password Generator - made with AI</title><content type='html'>&lt;div id=&quot;passwordGen&quot;&gt;
  &lt;style&gt;
    #passwordGen {
      font-family: Arial, sans-serif;
      max-width: 600px;
      margin: 20px auto;
      padding: 20px;
      border-radius: 8px;
      box-shadow: 0 0 10px rgba(0,0,0,0.1);
    }
    #passwordGen label {
      display: block;
      margin: 10px 0 5px;
    }
    #passwordGen input[type=&quot;range&quot;] {
      width: 100%;
    }
    #passwordGen .output-box {
      margin-top: 20px;
      padding: 10px;
      border: 1px solid #ccc;
      border-radius: 4px;
      background: #f9f9f9;
      word-break: break-all;
    }
    #passwordGen button {
      margin-top: 10px;
      padding: 10px 15px;
      cursor: pointer;
      border: none;
      background: #007BFF;
      color: white;
      border-radius: 5px;
    }
    #passwordGen button:active {
      transform: scale(0.98);
    }
  &lt;/style&gt;

  &lt;h2&gt;Secure Password Generator&lt;/h2&gt;

  &lt;label for=&quot;length&quot;&gt;Password Length (8 - 20): &lt;span id=&quot;lengthValue&quot;&gt;12&lt;/span&gt;&lt;/label&gt;
  &lt;input type=&quot;range&quot; id=&quot;length&quot; min=&quot;8&quot; max=&quot;20&quot; step=&quot;1&quot; value=&quot;12&quot;&gt;

  &lt;label&gt;&lt;input type=&quot;checkbox&quot; id=&quot;wildcards&quot; checked&gt; Include Wildcards (!, @, #, $, %, ^, &amp;, *)&lt;/label&gt;
  &lt;label&gt;&lt;input type=&quot;checkbox&quot; id=&quot;spaces&quot; checked&gt; Include Spaces&lt;/label&gt;

  &lt;button id=&quot;generateBtn&quot;&gt;Generate Password&lt;/button&gt;
  &lt;button id=&quot;copyBtn&quot;&gt;Copy to Clipboard&lt;/button&gt;

  &lt;div class=&quot;output-box&quot; id=&quot;passwordOutput&quot;&gt;Your secure password will appear here.&lt;/div&gt;

  &lt;script type=&quot;text/javascript&quot;&gt;
    (function () {
      const container = document.getElementById(&quot;passwordGen&quot;);
      const upper = &quot;ABCDEFGHIJKLMNOPQRSTUVWXYZ&quot;;
      const lower = &quot;abcdefghijklmnopqrstuvwxyz&quot;;
      const numbers = &quot;0123456789&quot;;
      const symbols = &quot;!@#$%^&amp;*&quot;;
      const space = &quot; &quot;;

      const lengthSlider = container.querySelector(&quot;#length&quot;);
      const lengthValue = container.querySelector(&quot;#lengthValue&quot;);
      const includeWildcards = container.querySelector(&quot;#wildcards&quot;);
      const includeSpaces = container.querySelector(&quot;#spaces&quot;);
      const outputBox = container.querySelector(&quot;#passwordOutput&quot;);
      const generateBtn = container.querySelector(&quot;#generateBtn&quot;);
      const copyBtn = container.querySelector(&quot;#copyBtn&quot;);

      lengthSlider.addEventListener(&quot;input&quot;, function () {
        lengthValue.textContent = this.value;
      });

      generateBtn.addEventListener(&quot;click&quot;, function () {
        const length = parseInt(lengthSlider.value);
        let charset = upper + lower + numbers;
        if (includeWildcards.checked) charset += symbols;
        if (includeSpaces.checked) charset += space;

        let password = &quot;&quot;;
        while (true) {
          password = &quot;&quot;;
          for (let i = 0; i &lt; length; i++) {
            password += charset[Math.floor(Math.random() * charset.length)];
          }
          if (validatePassword(password, includeWildcards.checked, includeSpaces.checked)) break;
        }

        outputBox.textContent = password;
      });

      copyBtn.addEventListener(&quot;click&quot;, function () {
        const text = outputBox.textContent;
        navigator.clipboard.writeText(text).then(() =&gt; {
          alert(&quot;Password copied to clipboard!&quot;);
        });
      });

      function validatePassword(pwd, includeWildcards, includeSpaces) {
        const hasUpper = /[A-Z]/.test(pwd);
        const hasLower = /[a-z]/.test(pwd);
        const hasNumber = /[0-9]/.test(pwd);
        const hasSymbol = /[!@#$%^&amp;*]/.test(pwd);
        const hasSpace = / /.test(pwd);

        if (!hasUpper || !hasLower || !hasNumber) return false;
        if (includeWildcards &amp;&amp; !hasSymbol) return false;
        if (includeSpaces &amp;&amp; !hasSpace) return false;
        return true;
      }
    })();
  &lt;/script&gt;
&lt;/div&gt;
&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;
Developer Notes &lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;This was really quick and easy to make (used ChatGPT), probably because it&#39;s a very simple operation with clear and easily definable paramaters.&lt;/span&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/4464778548012055505/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/secure-password-generator-made-with-ai.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4464778548012055505'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4464778548012055505'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/secure-password-generator-made-with-ai.html' title='Secure Password Generator - made with AI'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-1244043105936309504</id><published>2025-07-22T11:41:00.000-07:00</published><updated>2025-07-22T11:41:24.827-07:00</updated><title type='text'>Browser-based Fractal Explorer (made with AI)</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I decided to try creating something with Gemini instead of ChatGPT and have to say, this is pretty impressive as the result of a handful of prompts! I often got an error message in Gemini, but then it completed everything correctly anyway.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Although let&#39;s see how well it works within a Blogger post..&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I&#39;ve always been fascinated by fractals, but never really understood the maths of it well enough to code a generator myself. I added the colour-cycling because of my memories being used of the effect on the 16-bit computers (Neochrome&#39;s waterfall is probably the best-known example from the Atari SI).&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;



&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
    &lt;meta charset=&quot;UTF-8&quot;&gt;&lt;/meta&gt;
    &lt;meta content=&quot;width=device-width, initial-scale=1.0&quot; name=&quot;viewport&quot;&gt;&lt;/meta&gt;
    &lt;title&gt;Advanced Fractal Generator&lt;/title&gt;
    &lt;!-- Load Tailwind CSS for easy styling --&gt;
    &lt;script src=&quot;https://cdn.tailwindcss.com&quot;&gt;&lt;/script&gt;
    &lt;style&gt;
        /* Custom styles for the body and canvas */
        body {
            font-family: &#39;Inter&#39;, sans-serif; /* Use Inter font */
            display: flex;
            flex-direction: column;
            justify-content: center;
            align-items: center;
            min-height: 100vh;
            background-color: #1a202c; /* Dark background */
            color: #e2e8f0; /* Light text color */
            margin: 0;
            padding: 20px;
            box-sizing: border-box;
        }
        canvas {
            background-color: #2d3748; /* Slightly lighter dark background for canvas */
            display: block;
            border-radius: 1rem; /* Rounded corners for canvas */
            box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05); /* Subtle shadow */
            max-width: 90vw; /* Max width relative to viewport */
            max-height: 80vh; /* Max height relative to viewport */
        }
        .controls {
            margin-top: 20px;
            display: flex;
            flex-wrap: wrap;
            gap: 15px;
            justify-content: center;
            align-items: center;
            padding: 10px;
            background-color: #2d3748;
            border-radius: 0.75rem;
            box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);
        }
        .control-group {
            display: flex;
            flex-direction: column;
            align-items: center;
            gap: 5px;
        }
        input[type=&quot;range&quot;] {
            width: 150px;
            -webkit-appearance: none;
            height: 8px;
            background: #4a5568;
            border-radius: 5px;
            outline: none;
            transition: opacity .2s;
        }
        input[type=&quot;range&quot;]::-webkit-slider-thumb {
            -webkit-appearance: none;
            appearance: none;
            width: 20px;
            height: 20px;
            background: #63b3ed; /* Blue thumb */
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
        }
        input[type=&quot;range&quot;]::-moz-range-thumb {
            width: 20px;
            height: 20px;
            background: #63b3ed;
            border-radius: 50%;
            cursor: pointer;
            box-shadow: 0 0 2px rgba(0, 0, 0, 0.5);
        }
        select {
            background-color: #4a5568;
            color: #e2e8f0;
            border: 1px solid #4a5568;
            border-radius: 0.5rem;
            padding: 8px 12px;
            font-size: 1rem;
            cursor: pointer;
            outline: none;
            appearance: none; /* Remove default arrow */
            background-image: url(&#39;data:image/svg+xml;charset=US-ASCII,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%22292.4%22%20height%3D%22292.4%22%3E%3Cpath%20fill%3D%22%23e2e8f0%22%20d%3D%22M287%2069.4a17.6%2017.6%200%200%200-13-5.4H18.4c-6.5%200-12.3%203.2-16%208.7-3.6%205.4-3.6%2012.8%200%2018.3l128%20128c3.7%205.5%209.5%208.7%2016%208.7s12.3-3.2%2016-8.7l128-128c3.7-5.5%203.7-12.8%200-18.3z%22%2F%3E%3C%2Fsvg%3E&#39;);
            background-repeat: no-repeat;
            background-position: right 10px top 50%;
            background-size: 12px auto;
        }
        select:hover {
            border-color: #63b3ed;
        }
        input[type=&quot;checkbox&quot;] {
            width: 20px;
            height: 20px;
            accent-color: #63b3ed; /* Checkbox color */
        }
    &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;h1 class=&quot;text-3xl font-bold mb-6 text-white&quot;&gt;Fractal Generator&lt;/h1&gt;

    &lt;canvas id=&quot;fractalCanvas&quot;&gt;&lt;/canvas&gt;

    &lt;div class=&quot;controls&quot;&gt;
        &lt;div class=&quot;control-group&quot;&gt;
            &lt;label class=&quot;text-sm&quot; for=&quot;fractalType&quot;&gt;Fractal Type:&lt;/label&gt;
            &lt;select id=&quot;fractalType&quot;&gt;
                &lt;option value=&quot;tree&quot;&gt;Fractal Tree&lt;/option&gt;
                &lt;option value=&quot;mandelbrot&quot;&gt;Mandelbrot Set&lt;/option&gt;
                &lt;option value=&quot;julia&quot;&gt;Julia Set&lt;/option&gt;
            &lt;/select&gt;
        &lt;/div&gt;

        &lt;!-- Common Color Control --&gt;
        &lt;div class=&quot;control-group&quot;&gt;
            &lt;label class=&quot;text-sm&quot; for=&quot;hueSlider&quot;&gt;Color Hue: &lt;span id=&quot;hueValue&quot;&gt;&lt;/span&gt;°&lt;/label&gt;
            &lt;input id=&quot;hueSlider&quot; max=&quot;360&quot; min=&quot;0&quot; type=&quot;range&quot; value=&quot;120&quot; /&gt;
        &lt;/div&gt;

        &lt;!-- Color Cycling Controls (for Mandelbrot and Julia) --&gt;
        &lt;div class=&quot;flex flex-wrap gap-15 justify-center hidden&quot; id=&quot;colorCycleControls&quot;&gt;
            &lt;div class=&quot;control-group flex-row items-center gap-2&quot;&gt;
                &lt;input id=&quot;colorCycleCheckbox&quot; type=&quot;checkbox&quot; /&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;colorCycleCheckbox&quot;&gt;Color Cycling&lt;/label&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;cycleSpeedSlider&quot;&gt;Cycle Speed: &lt;span id=&quot;cycleSpeedValue&quot;&gt;&lt;/span&gt;s&lt;/label&gt;
                &lt;input id=&quot;cycleSpeedSlider&quot; max=&quot;3&quot; min=&quot;0.1&quot; step=&quot;0.1&quot; type=&quot;range&quot; value=&quot;1&quot; /&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;!-- Fractal Tree Controls --&gt;
        &lt;div class=&quot;flex flex-wrap gap-15 justify-center&quot; id=&quot;treeControls&quot;&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;depthSlider&quot;&gt;Depth: &lt;span id=&quot;depthValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;depthSlider&quot; max=&quot;16&quot; min=&quot;1&quot; type=&quot;range&quot; value=&quot;10&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;angleSlider&quot;&gt;Angle: &lt;span id=&quot;angleValue&quot;&gt;&lt;/span&gt;°&lt;/label&gt;
                &lt;input id=&quot;angleSlider&quot; max=&quot;60&quot; min=&quot;10&quot; type=&quot;range&quot; value=&quot;25&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;lengthReductionSlider&quot;&gt;Length Reduction: &lt;span id=&quot;lengthReductionValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;lengthReductionSlider&quot; max=&quot;0.9&quot; min=&quot;0.5&quot; step=&quot;0.01&quot; type=&quot;range&quot; value=&quot;0.7&quot; /&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;!-- Mandelbrot Controls --&gt;
        &lt;div class=&quot;flex flex-wrap gap-15 justify-center hidden&quot; id=&quot;mandelbrotControls&quot;&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;maxIterationsSlider&quot;&gt;Max Iterations: &lt;span id=&quot;maxIterationsValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;maxIterationsSlider&quot; max=&quot;500&quot; min=&quot;50&quot; step=&quot;10&quot; type=&quot;range&quot; value=&quot;100&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;zoomSlider&quot;&gt;Zoom: &lt;span id=&quot;zoomValue&quot;&gt;&lt;/span&gt;x&lt;/label&gt;
                &lt;input id=&quot;zoomSlider&quot; max=&quot;100&quot; min=&quot;0.1&quot; step=&quot;0.1&quot; type=&quot;range&quot; value=&quot;1&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;offsetXSlider&quot;&gt;Offset X: &lt;span id=&quot;offsetXValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;offsetXSlider&quot; max=&quot;1.5&quot; min=&quot;-2.5&quot; step=&quot;0.01&quot; type=&quot;range&quot; value=&quot;-0.5&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;offsetYSlider&quot;&gt;Offset Y: &lt;span id=&quot;offsetYValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;offsetYSlider&quot; max=&quot;1.5&quot; min=&quot;-1.5&quot; step=&quot;0.01&quot; type=&quot;range&quot; value=&quot;0&quot; /&gt;
            &lt;/div&gt;
        &lt;/div&gt;

        &lt;!-- Julia Set Controls --&gt;
        &lt;div class=&quot;flex flex-wrap gap-15 justify-center hidden&quot; id=&quot;juliaControls&quot;&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;juliaMaxIterationsSlider&quot;&gt;Max Iterations: &lt;span id=&quot;juliaMaxIterationsValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;juliaMaxIterationsSlider&quot; max=&quot;500&quot; min=&quot;50&quot; step=&quot;10&quot; type=&quot;range&quot; value=&quot;100&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;juliaZoomSlider&quot;&gt;Zoom: &lt;span id=&quot;juliaZoomValue&quot;&gt;&lt;/span&gt;x&lt;/label&gt;
                &lt;input id=&quot;juliaZoomSlider&quot; max=&quot;100&quot; min=&quot;0.1&quot; step=&quot;0.1&quot; type=&quot;range&quot; value=&quot;1&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;juliaCrSlider&quot;&gt;C Real: &lt;span id=&quot;juliaCrValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;juliaCrSlider&quot; max=&quot;1&quot; min=&quot;-1&quot; step=&quot;0.01&quot; type=&quot;range&quot; value=&quot;-0.7&quot; /&gt;
            &lt;/div&gt;
            &lt;div class=&quot;control-group&quot;&gt;
                &lt;label class=&quot;text-sm&quot; for=&quot;juliaCiSlider&quot;&gt;C Imaginary: &lt;span id=&quot;juliaCiValue&quot;&gt;&lt;/span&gt;&lt;/label&gt;
                &lt;input id=&quot;juliaCiSlider&quot; max=&quot;1&quot; min=&quot;-1&quot; step=&quot;0.00001&quot; type=&quot;range&quot; value=&quot;0.27015&quot; /&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;script&gt;
        // Get the canvas element and its 2D rendering context
        const canvas = document.getElementById(&#39;fractalCanvas&#39;);
        const ctx = canvas.getContext(&#39;2d&#39;);

        // Get control elements
        const fractalTypeSelect = document.getElementById(&#39;fractalType&#39;);

        // Common Color Control
        const hueSlider = document.getElementById(&#39;hueSlider&#39;);
        const hueValueSpan = document.getElementById(&#39;hueValue&#39;);

        // Color Cycling Controls
        const colorCycleControls = document.getElementById(&#39;colorCycleControls&#39;);
        const colorCycleCheckbox = document.getElementById(&#39;colorCycleCheckbox&#39;);
        const cycleSpeedSlider = document.getElementById(&#39;cycleSpeedSlider&#39;);
        const cycleSpeedValueSpan = document.getElementById(&#39;cycleSpeedValue&#39;);

        // Tree Controls
        const treeControls = document.getElementById(&#39;treeControls&#39;);
        const depthSlider = document.getElementById(&#39;depthSlider&#39;);
        const angleSlider = document.getElementById(&#39;angleSlider&#39;);
        const lengthReductionSlider = document.getElementById(&#39;lengthReductionSlider&#39;);

        const depthValueSpan = document.getElementById(&#39;depthValue&#39;);
        const angleValueSpan = document.getElementById(&#39;angleValue&#39;);
        const lengthReductionValueSpan = document.getElementById(&#39;lengthReductionValue&#39;);

        // Mandelbrot Controls
        const mandelbrotControls = document.getElementById(&#39;mandelbrotControls&#39;);
        const maxIterationsSlider = document.getElementById(&#39;maxIterationsSlider&#39;);
        const zoomSlider = document.getElementById(&#39;zoomSlider&#39;);
        const offsetXSlider = document.getElementById(&#39;offsetXSlider&#39;);
        const offsetYSlider = document.getElementById(&#39;offsetYSlider&#39;);

        const maxIterationsValueSpan = document.getElementById(&#39;maxIterationsValue&#39;);
        const zoomValueSpan = document.getElementById(&#39;zoomValue&#39;);
        const offsetXValueSpan = document.getElementById(&#39;offsetXValue&#39;);
        const offsetYValueSpan = document.getElementById(&#39;offsetYValue&#39;);

        // Julia Set Controls
        const juliaControls = document.getElementById(&#39;juliaControls&#39;);
        const juliaMaxIterationsSlider = document.getElementById(&#39;juliaMaxIterationsSlider&#39;);
        const juliaZoomSlider = document.getElementById(&#39;juliaZoomSlider&#39;);
        const juliaCrSlider = document.getElementById(&#39;juliaCrSlider&#39;);
        const juliaCiSlider = document.getElementById(&#39;juliaCiSlider&#39;);

        const juliaMaxIterationsValueSpan = document.getElementById(&#39;juliaMaxIterationsValue&#39;);
        const juliaZoomValueSpan = document.getElementById(&#39;juliaZoomValue&#39;);
        const juliaCrValueSpan = document.getElementById(&#39;juliaCrValue&#39;);
        const juliaCiValueSpan = document.getElementById(&#39;juliaCiValue&#39;);

        // Initial values for fractal parameters
        let currentFractalType = fractalTypeSelect.value;

        // Tree parameters
        let maxDepth = parseInt(depthSlider.value);
        let branchAngle = parseFloat(angleSlider.value) * (Math.PI / 180); // Convert degrees to radians
        let lengthReduction = parseFloat(lengthReductionSlider.value);

        // Mandelbrot parameters
        let mandelbrotMaxIterations = parseInt(maxIterationsSlider.value);
        let mandelbrotZoom = parseFloat(zoomSlider.value);
        let mandelbrotOffsetX = parseFloat(offsetXSlider.value);
        let mandelbrotOffsetY = parseFloat(offsetYSlider.value);

        // Julia parameters
        let juliaMaxIterations = parseInt(juliaMaxIterationsSlider.value);
        let juliaZoom = parseFloat(juliaZoomSlider.value);
        let juliaCr = parseFloat(juliaCrSlider.value);
        let juliaCi = parseFloat(juliaCiSlider.value);

        let baseHue = parseInt(hueSlider.value); // Common hue value

        // Color cycling variables
        let isColorCycling = colorCycleCheckbox.checked;
        let cycleSpeed = parseFloat(cycleSpeedSlider.value) * 1000; // Convert seconds to milliseconds
        let lastFrameTime = 0;
        let animationFrameId = null;

        /**
         * Updates the displayed value for a slider and redraws the fractal.
         * @param {HTMLElement} slider - The slider element.
         * @param {HTMLElement} valueSpan - The span element to display the value.
         * @param {string} unit - Optional unit to append to the value (e.g., &quot;°&quot;).
         */
        function updateSliderValue(slider, valueSpan, unit = &#39;&#39;) {
            valueSpan.textContent = slider.value + unit;
            // Update the global variables based on current slider values
            baseHue = parseInt(hueSlider.value); // Always update hue

            if (currentFractalType === &#39;tree&#39;) {
                maxDepth = parseInt(depthSlider.value);
                branchAngle = parseFloat(angleSlider.value) * (Math.PI / 180);
                lengthReduction = parseFloat(lengthReductionSlider.value);
            } else if (currentFractalType === &#39;mandelbrot&#39;) {
                mandelbrotMaxIterations = parseInt(maxIterationsSlider.value);
                mandelbrotZoom = parseFloat(zoomSlider.value);
                mandelbrotOffsetX = parseFloat(offsetXSlider.value);
                mandelbrotOffsetY = parseFloat(offsetYSlider.value);
            } else if (currentFractalType === &#39;julia&#39;) {
                juliaMaxIterations = parseInt(juliaMaxIterationsSlider.value);
                juliaZoom = parseFloat(juliaZoomSlider.value);
                juliaCr = parseFloat(juliaCrSlider.value);
                juliaCi = parseFloat(juliaCiSlider.value);
            }

            // Update color cycling speed if changed
            cycleSpeed = parseFloat(cycleSpeedSlider.value) * 1000;

            // Redraw the fractal
            drawFractal();
        }

        // Event listeners for sliders to update values and redraw
        depthSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(depthSlider, depthValueSpan));
        angleSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(angleSlider, angleValueSpan, &#39;°&#39;));
        lengthReductionSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(lengthReductionSlider, lengthReductionValueSpan));
        hueSlider.addEventListener(&#39;input&#39;, () =&gt; {
            // If color cycling is active, manually setting hue should stop it
            if (isColorCycling) {
                colorCycleCheckbox.checked = false;
                isColorCycling = false;
                cancelAnimationFrame(animationFrameId);
            }
            updateSliderValue(hueSlider, hueValueSpan, &#39;°&#39;);
        });

        maxIterationsSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(maxIterationsSlider, maxIterationsValueSpan));
        zoomSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(zoomSlider, zoomValueSpan, &#39;x&#39;));
        offsetXSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(offsetXSlider, offsetXValueSpan));
        offsetYSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(offsetYSlider, offsetYValueSpan));

        juliaMaxIterationsSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(juliaMaxIterationsSlider, juliaMaxIterationsValueSpan));
        juliaZoomSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(juliaZoomSlider, juliaZoomValueSpan, &#39;x&#39;));
        juliaCrSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(juliaCrSlider, juliaCrValueSpan));
        juliaCiSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(juliaCiSlider, juliaCiValueSpan));

        // Event listeners for color cycling controls
        colorCycleCheckbox.addEventListener(&#39;change&#39;, () =&gt; {
            isColorCycling = colorCycleCheckbox.checked;
            if (isColorCycling) {
                startColorCycling();
            } else {
                cancelAnimationFrame(animationFrameId);
            }
        });
        cycleSpeedSlider.addEventListener(&#39;input&#39;, () =&gt; updateSliderValue(cycleSpeedSlider, cycleSpeedValueSpan));


        /**
         * Toggles the visibility of control groups based on the selected fractal type.
         */
        function updateControlVisibility() {
            currentFractalType = fractalTypeSelect.value;
            treeControls.classList.add(&#39;hidden&#39;);
            mandelbrotControls.classList.add(&#39;hidden&#39;);
            juliaControls.classList.add(&#39;hidden&#39;);
            colorCycleControls.classList.add(&#39;hidden&#39;); // Hide color cycle controls by default

            // Stop color cycling if changing fractal type
            cancelAnimationFrame(animationFrameId);
            colorCycleCheckbox.checked = false;
            isColorCycling = false;

            if (currentFractalType === &#39;tree&#39;) {
                treeControls.classList.remove(&#39;hidden&#39;);
            } else if (currentFractalType === &#39;mandelbrot&#39;) {
                mandelbrotControls.classList.remove(&#39;hidden&#39;);
                colorCycleControls.classList.remove(&#39;hidden&#39;); // Show color cycle controls
            } else if (currentFractalType === &#39;julia&#39;) {
                juliaControls.classList.remove(&#39;hidden&#39;);
                colorCycleControls.classList.remove(&#39;hidden&#39;); // Show color cycle controls
            }
            drawFractal(); // Redraw the fractal after changing type
        }

        // Event listener for fractal type dropdown
        fractalTypeSelect.addEventListener(&#39;change&#39;, updateControlVisibility);

        /**
         * Sets up the canvas size to be responsive to the window size.
         * This function is called on window load and resize.
         */
        function setupCanvas() {
            // Define the maximum width for the canvas (e.g., for a Blogger post)
            const maxCanvasWidth = 1280;
            const aspectRatio = 16 / 9; // Example aspect ratio

            // Calculate initial width based on window size, but cap it at maxCanvasWidth
            let width = Math.min(window.innerWidth * 0.8, maxCanvasWidth);
            let height = width / aspectRatio;

            // If height is too tall for the current window, adjust based on height
            if (height &gt; window.innerHeight * 0.7) {
                height = window.innerHeight * 0.7;
                width = height * aspectRatio;
            }

            canvas.width = width;
            canvas.height = height;

            // Initial display of slider values for all controls
            updateSliderValue(depthSlider, depthValueSpan);
            updateSliderValue(angleSlider, angleValueSpan, &#39;°&#39;);
            updateSliderValue(lengthReductionSlider, lengthReductionValueSpan);
            updateSliderValue(hueSlider, hueValueSpan, &#39;°&#39;);

            updateSliderValue(maxIterationsSlider, maxIterationsValueSpan);
            updateSliderValue(zoomSlider, zoomValueSpan, &#39;x&#39;);
            updateSliderValue(offsetXSlider, offsetXValueSpan);
            updateSliderValue(offsetYSlider, offsetYValueSpan);

            updateSliderValue(juliaMaxIterationsSlider, juliaMaxIterationsValueSpan);
            updateSliderValue(juliaZoomSlider, juliaZoomValueSpan, &#39;x&#39;);
            updateSliderValue(juliaCrSlider, juliaCrValueSpan);
            updateSliderValue(juliaCiSlider, juliaCiValueSpan);

            updateSliderValue(cycleSpeedSlider, cycleSpeedValueSpan); // Display initial cycle speed

            updateControlVisibility(); // Set initial control visibility and draw
        }

        /**
         * Recursively draws a fractal tree branch.
         * @param {number} x1 - Starting X coordinate of the branch.
         * @param {number} y1 - Starting Y coordinate of the branch.
         * @param {number} length - Length of the current branch.
         * @param {number} angle - Angle of the current branch in radians.
         * @param {number} depth - Current recursion depth.
         */
        function drawFractalTreeInternal(x1, y1, length, angle, depth) {
            // Base case: Stop drawing if depth is 0 or length is too small
            if (depth === 0 || length &lt; 2) {
                return;
            }

            // Calculate the end point of the current branch
            const x2 = x1 + length * Math.cos(angle);
            const y2 = y1 + length * Math.sin(angle);

            // Set line properties based on depth for visual variation and the selected hue
            ctx.beginPath();
            ctx.moveTo(x1, y1);
            ctx.lineTo(x2, y2);
            ctx.lineWidth = Math.max(1, depth / 2); // Thicker lines for lower depths (closer to trunk)

            // Adjust saturation and lightness based on depth for a more natural look
            const saturation = Math.min(100, 50 + depth * 5);
            const lightness = Math.min(100, 20 + depth * 5);

            // Use the baseHue for the main branches
            ctx.strokeStyle = `hsl(${baseHue}, ${saturation}%, ${lightness}%)`;

            // For the tips (lower depths), shift the hue slightly to simulate different colors (e.g., autumn leaves)
            if (depth &lt; 3) {
                // Shift hue by a small amount for tips, wrapping around 360
                const tipHue = (baseHue + 30) % 360;
                ctx.strokeStyle = `hsl(${tipHue}, ${saturation}%, ${lightness}%)`;
            }
            ctx.stroke();

            // Recursively draw two new branches
            // First branch: rotated by -branchAngle
            drawFractalTreeInternal(x2, y2, length * lengthReduction, angle - branchAngle, depth - 1);
            // Second branch: rotated by +branchAngle
            drawFractalTreeInternal(x2, y2, length * lengthReduction, angle + branchAngle, depth - 1);
        }

        /**
         * Draws the Mandelbrot Set.
         */
        function drawMandelbrot() {
            const imageData = ctx.createImageData(canvas.width, canvas.height);
            const data = imageData.data;

            const scale = 4 / mandelbrotZoom; // Adjust scale based on zoom level
            const centerX = mandelbrotOffsetX; // Center X of the complex plane
            const centerY = mandelbrotOffsetY; // Center Y of the complex plane

            for (let x = 0; x &lt; canvas.width; x++) {
                for (let y = 0; y &lt; canvas.height; y++) {
                    // Map pixel coordinates to complex plane coordinates
                    const cr = (x - canvas.width / 2) * scale / canvas.width + centerX;
                    const ci = (y - canvas.height / 2) * scale / canvas.height + centerY;

                    let zr = 0;
                    let zi = 0;
                    let i = 0;

                    // Mandelbrot iteration
                    while (zr * zr + zi * zi &lt; 4 &amp;&amp; i &lt; mandelbrotMaxIterations) {
                        const tempZr = zr * zr - zi * zi + cr;
                        zi = 2 * zr * zi + ci;
                        zr = tempZr;
                        i++;
                    }

                    const pixelIndex = (x + y * canvas.width) * 4;

                    if (i === mandelbrotMaxIterations) {
                        // Point is in the Mandelbrot set (black)
                        data[pixelIndex] = 0;     // R
                        data[pixelIndex + 1] = 0; // G
                        data[pixelIndex + 2] = 0; // B
                        data[pixelIndex + 3] = 255; // A
                    } else {
                        // Point is outside, color based on iteration count (hue-shifted)
                        const hue = (baseHue + (i * 360 / mandelbrotMaxIterations)) % 360;
                        const saturation = 100;
                        const lightness = 50;

                        // Convert HSL to RGB
                        const c = (1 - Math.abs(2 * lightness / 100 - 1)) * saturation / 100;
                        const x_hsl = c * (1 - Math.abs((hue / 60) % 2 - 1));
                        const m = lightness / 100 - c / 2;
                        let r = 0, g = 0, b = 0;

                        if (0 &lt;= hue &amp;&amp; hue &lt; 60) {
                            r = c; g = x_hsl; b = 0;
                        } else if (60 &lt;= hue &amp;&amp; hue &lt; 120) {
                            r = x_hsl; g = c; b = 0;
                        } else if (120 &lt;= hue &amp;&amp; hue &lt; 180) {
                            r = 0; g = c; b = x_hsl;
                        } else if (180 &lt;= hue &amp;&amp; hue &lt; 240) {
                            r = 0; g = x_hsl; b = c;
                        } else if (240 &lt;= hue &amp;&amp; hue &lt; 300) {
                            r = x_hsl; g = 0; b = c;
                        } else if (300 &lt;= hue &amp;&amp; hue &lt; 360) {
                            r = c; g = 0; b = x_hsl;
                        }

                        data[pixelIndex] = Math.round((r + m) * 255);     // R
                        data[pixelIndex + 1] = Math.round((g + m) * 255); // G
                        data[pixelIndex + 2] = Math.round((b + m) * 255); // B
                        data[pixelIndex + 3] = 255; // A
                    }
                }
            }
            ctx.putImageData(imageData, 0, 0);
        }

        /**
         * Draws the Julia Set.
         */
        function drawJuliaSet() {
            const imageData = ctx.createImageData(canvas.width, canvas.height);
            const data = imageData.data;

            const scale = 4 / juliaZoom; // Adjust scale based on zoom level
            const c_real = juliaCr;
            const c_imag = juliaCi;

            for (let x = 0; x &lt; canvas.width; x++) {
                for (let y = 0; y &lt; canvas.height; y++) {
                    // Map pixel coordinates to complex plane coordinates
                    let zr = (x - canvas.width / 2) * scale / canvas.width;
                    let zi = (y - canvas.height / 2) * scale / canvas.height;

                    let i = 0;

                    // Julia iteration
                    while (zr * zr + zi * zi &lt; 4 &amp;&amp; i &lt; juliaMaxIterations) {
                        const tempZr = zr * zr - zi * zi + c_real;
                        zi = 2 * zr * zi + c_imag;
                        zr = tempZr;
                        i++;
                    }

                    const pixelIndex = (x + y * canvas.width) * 4;

                    if (i === juliaMaxIterations) {
                        // Point is in the Julia set (black)
                        data[pixelIndex] = 0;     // R
                        data[pixelIndex + 1] = 0; // G
                        data[pixelIndex + 2] = 0; // B
                        data[pixelIndex + 3] = 255; // A
                    } else {
                        // Point is outside, color based on iteration count (hue-shifted)
                        const hue = (baseHue + (i * 360 / juliaMaxIterations)) % 360;
                        const saturation = 100;
                        const lightness = 50;

                        // Convert HSL to RGB
                        const c = (1 - Math.abs(2 * lightness / 100 - 1)) * saturation / 100;
                        const x_hsl = c * (1 - Math.abs((hue / 60) % 2 - 1));
                        const m = lightness / 100 - c / 2;
                        let r = 0, g = 0, b = 0;

                        if (0 &lt;= hue &amp;&amp; hue &lt; 60) {
                            r = c; g = x_hsl; b = 0;
                        } else if (60 &lt;= hue &amp;&amp; hue &lt; 120) {
                            r = x_hsl; g = c; b = 0;
                        } else if (120 &lt;= hue &amp;&amp; hue &lt; 180) {
                            r = 0; g = c; b = x_hsl;
                        } else if (180 &lt;= hue &amp;&amp; hue &lt; 240) {
                            r = 0; g = x_hsl; b = c;
                        } else if (240 &lt;= hue &amp;&amp; hue &lt; 300) {
                            r = x_hsl; g = 0; b = c;
                        } else if (300 &lt;= hue &amp;&amp; hue &lt; 360) {
                            r = c; g = 0; b = x_hsl;
                        }

                        data[pixelIndex] = Math.round((r + m) * 255);     // R
                        data[pixelIndex + 1] = Math.round((g + m) * 255); // G
                        data[pixelIndex + 2] = Math.round((b + m) * 255); // B
                        data[pixelIndex + 3] = 255; // A
                    }
                }
            }
            ctx.putImageData(imageData, 0, 0);
        }

        /**
         * Clears the canvas and initiates the drawing of the selected fractal.
         */
        function drawFractal() {
            // Clear the entire canvas before drawing a new fractal
            ctx.clearRect(0, 0, canvas.width, canvas.height);

            if (currentFractalType === &#39;tree&#39;) {
                ctx.lineCap = &#39;round&#39;; // Rounded line caps for a softer look
                const initialLength = canvas.height / 4;
                drawFractalTreeInternal(canvas.width / 2, canvas.height, initialLength, -Math.PI / 2, maxDepth);
            } else if (currentFractalType === &#39;mandelbrot&#39;) {
                drawMandelbrot();
            } else if (currentFractalType === &#39;julia&#39;) {
                drawJuliaSet();
            }
        }

        /**
         * Animation loop for color cycling.
         * @param {DOMHighResTimeStamp} currentTime - The current time provided by requestAnimationFrame.
         */
        function animateColorCycle(currentTime) {
            if (!lastFrameTime) lastFrameTime = currentTime;
            const deltaTime = currentTime - lastFrameTime;

            // Update hue based on delta time and cycle speed
            // Ensure cycleSpeed is not zero to prevent division by zero
            if (cycleSpeed &gt; 0) {
                baseHue = (baseHue + (deltaTime / cycleSpeed) * 360) % 360;
                hueSlider.value = baseHue; // Update slider to reflect animated hue
                hueValueSpan.textContent = Math.round(baseHue) + &#39;°&#39;;
            }


            drawFractal(); // Redraw the fractal with the new hue

            lastFrameTime = currentTime;
            animationFrameId = requestAnimationFrame(animateColorCycle);
        }

        /**
         * Starts the color cycling animation.
         */
        function startColorCycling() {
            if (animationFrameId) {
                cancelAnimationFrame(animationFrameId); // Stop any existing animation
            }
            lastFrameTime = 0; // Reset last frame time for smooth start
            animationFrameId = requestAnimationFrame(animateColorCycle);
        }

        // Event listeners for window load and resize to ensure responsiveness
        window.addEventListener(&#39;load&#39;, setupCanvas);
        window.addEventListener(&#39;resize&#39;, setupCanvas);
    &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/1244043105936309504/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/browser-based-fractal-explorer-made.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/1244043105936309504'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/1244043105936309504'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/browser-based-fractal-explorer-made.html' title='Browser-based Fractal Explorer (made with AI)'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-5254513630733388905</id><published>2025-07-20T10:26:00.000-07:00</published><updated>2025-07-20T10:26:02.498-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="flow problem"/><category scheme="http://www.blogger.com/atom/ns#" term="freeware game"/><category scheme="http://www.blogger.com/atom/ns#" term="physics"/><category scheme="http://www.blogger.com/atom/ns#" term="puzzle game"/><category scheme="http://www.blogger.com/atom/ns#" term="retro"/><category scheme="http://www.blogger.com/atom/ns#" term="version update"/><category scheme="http://www.blogger.com/atom/ns#" term="water physics"/><title type='text'>Flow Problem - updated version available</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&amp;nbsp;I&#39;ve just posted a big update to this game.&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;a href=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZx0_VHpLWAb2CMmXRqPwQhaYOqhMqQASS84DiVjncHImz-mr-zJzT8YqZOJgUHps4osKYgsQM-xWHLgDJ_BMy6Ihl74iPOVlgltjH88SUjJKxRSeJ_UKeCdKdMb3nVHij9qKyA5AT-MgScC2YHwY1_ye0ql1WXeYcFi_YFzd9eRxTKzYDncNV1dHbpmQ/s1602/5.png&quot; imageanchor=&quot;1&quot; style=&quot;margin-left: 1em; margin-right: 1em;&quot;&gt;&lt;img border=&quot;0&quot; data-original-height=&quot;1032&quot; data-original-width=&quot;1602&quot; height=&quot;412&quot; src=&quot;https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZx0_VHpLWAb2CMmXRqPwQhaYOqhMqQASS84DiVjncHImz-mr-zJzT8YqZOJgUHps4osKYgsQM-xWHLgDJ_BMy6Ihl74iPOVlgltjH88SUjJKxRSeJ_UKeCdKdMb3nVHij9qKyA5AT-MgScC2YHwY1_ye0ql1WXeYcFi_YFzd9eRxTKzYDncNV1dHbpmQ/w640-h412/5.png&quot; width=&quot;640&quot; /&gt;&lt;/a&gt;&lt;/div&gt;&lt;br /&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Music and sound effects added&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;QOL enhancements (you may or may not notice them, but they&#39;re there!!)&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I&#39;ve really worked on optimising the code for the main game loop, resulting in a big frame rate improvement - on my PC it now maintains a pretty constant frame rate of 60fps, whereas in the initial build it usually sat between 45-50fps&lt;/span&gt;&lt;/li&gt;&lt;li&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Native Linux build added&lt;/span&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Whether you&#39;ve played it already or not, I hope you enjoy the results of this update!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://captaind.itch.io/flow-problem&quot; target=&quot;_blank&quot;&gt;Get it on Itch&lt;/a&gt;&lt;/b&gt; &lt;/span&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;&lt;i&gt;(it&#39;s free)&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/5254513630733388905/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flow-problem-updated-version-available.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5254513630733388905'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/5254513630733388905'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flow-problem-updated-version-available.html' title='Flow Problem - updated version available'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><media:thumbnail xmlns:media="http://search.yahoo.com/mrss/" url="https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEgZx0_VHpLWAb2CMmXRqPwQhaYOqhMqQASS84DiVjncHImz-mr-zJzT8YqZOJgUHps4osKYgsQM-xWHLgDJ_BMy6Ihl74iPOVlgltjH88SUjJKxRSeJ_UKeCdKdMb3nVHij9qKyA5AT-MgScC2YHwY1_ye0ql1WXeYcFi_YFzd9eRxTKzYDncNV1dHbpmQ/s72-w640-h412-c/5.png" height="72" width="72"/><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-8964256882501730235</id><published>2025-07-19T11:23:00.000-07:00</published><updated>2025-07-19T11:23:48.197-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="ags"/><category scheme="http://www.blogger.com/atom/ns#" term="flow problem"/><category scheme="http://www.blogger.com/atom/ns#" term="freeware game"/><category scheme="http://www.blogger.com/atom/ns#" term="physics"/><category scheme="http://www.blogger.com/atom/ns#" term="puzzle game"/><category scheme="http://www.blogger.com/atom/ns#" term="version update"/><title type='text'>Flow Problem update imminent</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;Within the next few days I&#39;ll launch the updated version of Flow Problem - containing QOL improvements, improved performance, music and sound effects - and, thanks to a slight issue I noticed, even improved physics! (You may never notice this bit though 🤣 )&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: medium;&quot;&gt;I aim to release a Linux version as well, as long as I can confirm with someone that it works okay. I may potentially look into tweaking a version for Android and maybe release a web-version, but for now I&#39;m sticking with Windows and (hopefully) Linux. I need to redesign the Itch page as well because it was a real rush job with little to no thought put into it, which was fine for a quick experiment, but since I&#39;ve taken the time to update it, I&#39;ll update the page as well!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/8964256882501730235/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flow-problem-update-imminent.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/8964256882501730235'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/8964256882501730235'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flow-problem-update-imminent.html' title='Flow Problem update imminent'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-1062862347641035331</id><published>2025-07-19T10:11:00.000-07:00</published><updated>2025-07-19T11:15:39.904-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="death has a million stomping boots"/><category scheme="http://www.blogger.com/atom/ns#" term="discount"/><category scheme="http://www.blogger.com/atom/ns#" term="flash sale"/><category scheme="http://www.blogger.com/atom/ns#" term="sale"/><category scheme="http://www.blogger.com/atom/ns#" term="snow problem"/><category scheme="http://www.blogger.com/atom/ns#" term="special offer"/><category scheme="http://www.blogger.com/atom/ns#" term="the ancient art of staying alive"/><title type='text'>Flash Sale Alert!</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;In case you missed them in the Steam Summer Sale, or just prefer to get them on Itch...&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;b&gt;All 75% off the normal price:&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;ul style=&quot;text-align: left;&quot;&gt;&lt;li style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/captain-disaster-in-death-has-a-million-stomping-boots&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Captain Disaster in: Death Has A Million Stomping Boots&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/snow-problem&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Snow Problem&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;li style=&quot;text-align: justify;&quot;&gt;&lt;a href=&quot;https://captaind.itch.io/the-ancient-art-of-staying-alive&quot; target=&quot;_blank&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;The Ancient Art of Staying Alive&lt;/span&gt;&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;Or, &lt;a href=&quot;https://itch.io/s/157200/flash-sale&quot; target=&quot;_blank&quot;&gt;pick up all 3 for just $4.99!&lt;/a&gt;!&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;span style=&quot;font-size: large;&quot;&gt;This sale runs over the weekend only.&lt;/span&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/1062862347641035331/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flash-sale-alert.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/1062862347641035331'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/1062862347641035331'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/flash-sale-alert.html' title='Flash Sale Alert!'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-4747289566472912238</id><published>2025-07-18T11:02:00.000-07:00</published><updated>2025-07-18T11:02:17.300-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="colourblind mode"/><category scheme="http://www.blogger.com/atom/ns#" term="colourdoku"/><category scheme="http://www.blogger.com/atom/ns#" term="experiment"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><title type='text'>Colourdoku - experimental build</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;I want to assess this for playability and, in particular, whether the &quot;colour-blind mode&quot; actually helps people with colour-blindness. (I asked AI to add this feature in but didn&#39;t specify how it should work, and to be honest, I&#39;m not sure myself how it should work!)&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;This is the 16th iteration since I began developing this game with AI. Full developer notes will accompany the final release, but if you&#39;re interested, you can &lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html&quot;&gt;read more about my experiment of creating JavaScript games with AI&lt;/a&gt;. This particular game, obviously a version of Sudoku but with colours (and not strictly using Sudoku rules), is based off an earlier game I made with AGS years ago (I hate to think what my coding looked like at the time) - apparently this game still &lt;a href=&quot;https://gamejolt.com/games/colour-clash/151664&quot; target=&quot;_blank&quot;&gt;lurks on GameJolt&lt;/a&gt;!&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;The object of the game is simple - don&#39;t repeat a colour in the same vertical or horizontal row.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;!DOCTYPE html&gt;
&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot; /&gt;
  &lt;meta name=&quot;viewport&quot; content=&quot;width=device-width, initial-scale=1.0&quot; /&gt;
  &lt;title&gt;Colourdoku&lt;/title&gt;
  &lt;style&gt;
    body {
      font-family: Arial, sans-serif;
      text-align: center;
      background-color: #f0f0f0;
    }
    #board {
      display: grid;
      margin: 20px auto;
      gap: 1px;
      background-color: #000;
      max-width: 90vmin;
    }
    .cell {
      width: 100%;
      aspect-ratio: 1;
      background-color: white;
      border: 1px solid #000;
      cursor: pointer;
    }
    .palette {
      margin-top: 20px;
    }
    .palette-cell {
      display: inline-block;
      width: 30px;
      height: 30px;
      margin: 5px;
      border: 1px solid #000;
      cursor: pointer;
    }
    .selected {
      outline: 3px solid black;
    }
    .color-blind {
      background-image: repeating-linear-gradient(
        45deg,
        rgba(255,255,255,0.3) 0,
        rgba(255,255,255,0.3) 5px,
        rgba(0,0,0,0.2) 5px,
        rgba(0,0,0,0.2) 10px
      );
    }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Colourdoku&lt;/h1&gt;
  &lt;div&gt;
    Grid size:
    &lt;select id=&quot;gridSize&quot;&gt;
      &lt;script&gt;document.write([...Array(8).keys()].map(i =&gt; `&lt;option value=&quot;${i+5}&quot;&gt;${i+5}x${i+5}&lt;/option&gt;`).join(&#39;&#39;));&lt;/script&gt;
    &lt;/select&gt;
    Difficulty:
    &lt;select id=&quot;difficulty&quot;&gt;
      &lt;option value=&quot;0.1&quot;&gt;Easy&lt;/option&gt;
      &lt;option value=&quot;0.2&quot;&gt;Medium&lt;/option&gt;
      &lt;option value=&quot;0.3&quot;&gt;Hard&lt;/option&gt;
    &lt;/select&gt;
    Hint Display Time:
    &lt;select id=&quot;hintTime&quot;&gt;
      &lt;option value=&quot;1000&quot;&gt;Short&lt;/option&gt;
      &lt;option value=&quot;2000&quot;&gt;Medium&lt;/option&gt;
      &lt;option value=&quot;3000&quot;&gt;Long&lt;/option&gt;
    &lt;/select&gt;
    &lt;button onclick=&quot;generateBoard()&quot;&gt;Restart&lt;/button&gt;
    &lt;button onclick=&quot;checkBoard()&quot;&gt;Check&lt;/button&gt;
    &lt;button onclick=&quot;toggleColorBlindMode()&quot;&gt;Toggle Colour-blind Mode&lt;/button&gt;
  &lt;/div&gt;

  &lt;div id=&quot;board&quot;&gt;&lt;/div&gt;
  &lt;div class=&quot;palette&quot; id=&quot;palette&quot;&gt;&lt;/div&gt;

  &lt;script&gt;
    const COLORS = [&quot;red&quot;, &quot;blue&quot;, &quot;green&quot;, &quot;orange&quot;, &quot;purple&quot;, &quot;cyan&quot;, &quot;yellow&quot;, &quot;pink&quot;, &quot;brown&quot;, &quot;lime&quot;, &quot;magenta&quot;, &quot;teal&quot;];
    let selectedColor = null;
    let board = [];
    let original = [];
    let colorBlind = false;

    function createPalette(size) {
      const palette = document.getElementById(&quot;palette&quot;);
      palette.innerHTML = &quot;&quot;;
      const eraser = document.createElement(&quot;div&quot;);
      eraser.className = &quot;palette-cell&quot;;
      eraser.style.backgroundColor = &quot;#eee&quot;;
      eraser.innerText = &quot;×&quot;;
      eraser.onclick = () =&gt; { selectedColor = null; setSelected(eraser); };
      palette.appendChild(eraser);
      for (let i = 0; i &lt; size; i++) {
        const cell = document.createElement(&quot;div&quot;);
        cell.className = &quot;palette-cell&quot;;
        cell.style.backgroundColor = COLORS[i];
        cell.onclick = () =&gt; { selectedColor = COLORS[i]; setSelected(cell); };
        palette.appendChild(cell);
      }
    }

    function setSelected(element) {
      document.querySelectorAll(&quot;.palette-cell&quot;).forEach(el =&gt; el.classList.remove(&quot;selected&quot;));
      element.classList.add(&quot;selected&quot;);
    }

    function generateBoard() {
      const size = parseInt(document.getElementById(&quot;gridSize&quot;).value);
      const difficulty = parseFloat(document.getElementById(&quot;difficulty&quot;).value);
      board = Array.from({ length: size }, () =&gt; Array(size).fill(null));
      original = Array.from({ length: size }, () =&gt; Array(size).fill(false));
      const boardContainer = document.getElementById(&quot;board&quot;);
      boardContainer.innerHTML = &quot;&quot;;
      boardContainer.style.gridTemplateColumns = `repeat(${size}, 1fr)`;

      const colors = COLORS.slice(0, size);
      let attempts = 0;
      let cellsToFill = Math.floor(size * size * difficulty);

      while (attempts &lt; 1000 &amp;&amp; cellsToFill &gt; 0) {
        attempts++;
        const row = Math.floor(Math.random() * size);
        const col = Math.floor(Math.random() * size);
        if (!board[row][col]) {
          const available = colors.filter(color =&gt;
            !board[row].includes(color) &amp;&amp;
            !board.map(r =&gt; r[col]).includes(color)
          );
          if (available.length &gt; 0) {
            const color = available[Math.floor(Math.random() * available.length)];
            board[row][col] = color;
            original[row][col] = true;
            cellsToFill--;
          }
        }
      }

      for (let i = 0; i &lt; size; i++) {
        for (let j = 0; j &lt; size; j++) {
          const cell = document.createElement(&quot;div&quot;);
          cell.className = &quot;cell&quot;;
          if (board[i][j]) {
            cell.style.backgroundColor = board[i][j];
            if (colorBlind) cell.classList.add(&quot;color-blind&quot;);
          }
          cell.onclick = () =&gt; {
            if (original[i][j]) return;
            board[i][j] = selectedColor;
            cell.style.backgroundColor = selectedColor || &quot;white&quot;;
            if (colorBlind &amp;&amp; selectedColor) cell.classList.add(&quot;color-blind&quot;);
            else cell.classList.remove(&quot;color-blind&quot;);
          };
          boardContainer.appendChild(cell);
        }
      }

      createPalette(size);
    }

    function checkBoard() {
      const size = board.length;
      const hintTime = parseInt(document.getElementById(&quot;hintTime&quot;).value);
      const cells = document.querySelectorAll(&quot;#board .cell&quot;);
      for (let i = 0; i &lt; size; i++) {
        for (let j = 0; j &lt; size; j++) {
          const cell = cells[i * size + j];
          const color = board[i][j];
          if (!color) {
            cell.style.borderColor = &quot;orange&quot;;
          } else if (
            board[i].filter((c, idx) =&gt; c === color &amp;&amp; idx !== j).length &gt; 0 ||
            board.map(r =&gt; r[j]).filter((c, idx) =&gt; c === color &amp;&amp; idx !== i).length &gt; 0
          ) {
            cell.style.borderColor = &quot;red&quot;;
          } else {
            cell.style.borderColor = &quot;green&quot;;
          }
        }
      }
      setTimeout(() =&gt; {
        document.querySelectorAll(&quot;.cell&quot;).forEach(cell =&gt; cell.style.borderColor = &quot;#000&quot;);
      }, hintTime);
    }

    function toggleColorBlindMode() {
      colorBlind = !colorBlind;
      document.querySelectorAll(&quot;.cell&quot;).forEach(cell =&gt; {
        if (colorBlind &amp;&amp; cell.style.backgroundColor &amp;&amp; cell.style.backgroundColor !== &quot;white&quot;) {
          cell.classList.add(&quot;color-blind&quot;);
        } else {
          cell.classList.remove(&quot;color-blind&quot;);
        }
      });
    }

    window.onload = generateBoard;
  &lt;/script&gt;
&lt;/body&gt;
&lt;/html&gt;
</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/4747289566472912238/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/colourdoku-experimental-build.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4747289566472912238'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4747289566472912238'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/colourdoku-experimental-build.html' title='Colourdoku - experimental build'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-4248700504005135602</id><published>2025-07-15T08:09:00.000-07:00</published><updated>2025-07-15T08:13:47.008-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="chat gpt"/><category scheme="http://www.blogger.com/atom/ns#" term="free online game"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="puzzle game"/><title type='text'>Noughts and Crosses and Wildcards, Oh My! - a JavaScript game created using AI tools</title><content type='html'>

&lt;html lang=&quot;en&quot;&gt;
&lt;head&gt;
  &lt;meta charset=&quot;UTF-8&quot;&gt;&lt;/meta&gt;
  &lt;meta content=&quot;width=device-width, initial-scale=1.0&quot; name=&quot;viewport&quot;&gt;&lt;/meta&gt;
  &lt;title&gt;Wildcard Grid Game&lt;/title&gt;
  &lt;style&gt;
    body { font-family: sans-serif; text-align: center; padding: 1rem; }
    #gameContainer { display: flex; justify-content: center; gap: 2rem; flex-wrap: wrap; }
    #leftPanel { display: flex; flex-direction: column; align-items: center; }
    #board { display: grid; grid-template-columns: repeat(6, 60px); gap: 4px; margin: 20px auto; }
    .cell { width: 60px; height: 60px; border: 1px solid #333; font-size: 24px; font-weight: bold; line-height: 60px; background-color: white; cursor: pointer; }
    .wildcard { background-color: #f0e68c; }
    .highlight { background-color: lightgreen; }
    #controls { margin: 1rem auto; }
    #logContainer { width: 250px; text-align: left; }
    #log { max-height: 400px; overflow-y: auto; border: 1px solid #aaa; padding: 10px; background: #f8f8f8; }
    #rules { text-align: left; max-width: 800px; margin: 2rem auto; }
  &lt;/style&gt;
&lt;/head&gt;
&lt;body&gt;
  &lt;h1&gt;Wildcard Grid Game&lt;/h1&gt;
  &lt;div id=&quot;controls&quot;&gt;
    &lt;label for=&quot;aiStyle&quot;&gt;AI Style: &lt;/label&gt;
    &lt;select id=&quot;aiStyle&quot;&gt;
      &lt;option value=&quot;balanced&quot;&gt;Balanced&lt;/option&gt;
      &lt;option value=&quot;offensive&quot;&gt;Offensive&lt;/option&gt;
      &lt;option value=&quot;defensive&quot;&gt;Defensive&lt;/option&gt;
    &lt;/select&gt;
    &lt;label for=&quot;difficulty&quot;&gt;Difficulty: &lt;/label&gt;
    &lt;select id=&quot;difficulty&quot;&gt;
      &lt;option value=&quot;easy&quot;&gt;Easy&lt;/option&gt;
      &lt;option selected=&quot;&quot; value=&quot;normal&quot;&gt;Normal&lt;/option&gt;
      &lt;option value=&quot;hard&quot;&gt;Hard&lt;/option&gt;
    &lt;/select&gt;
    &lt;button onclick=&quot;resetGame()&quot;&gt;Reset Game&lt;/button&gt;
  &lt;/div&gt;
  &lt;div id=&quot;gameContainer&quot;&gt;
    &lt;div id=&quot;leftPanel&quot;&gt;
      &lt;div id=&quot;board&quot;&gt;&lt;/div&gt;
      &lt;p&gt;&lt;strong&gt;Scores&lt;/strong&gt; — Player: &lt;span id=&quot;playerScore&quot;&gt;0&lt;/span&gt; | AI: &lt;span id=&quot;aiScore&quot;&gt;0&lt;/span&gt;&lt;/p&gt;
      &lt;p id=&quot;gameStatus&quot;&gt;&lt;/p&gt;
    &lt;/div&gt;
    &lt;div id=&quot;logContainer&quot;&gt;
      &lt;h3&gt;AI Move Log&lt;/h3&gt;
      &lt;div id=&quot;log&quot;&gt;&lt;/div&gt;
    &lt;/div&gt;
  &lt;/div&gt;
  &lt;div id=&quot;rules&quot;&gt;
    &lt;h3&gt;Game Rules&lt;/h3&gt;
    &lt;ul&gt;
      &lt;li&gt;6x6 grid&lt;/li&gt;
      &lt;li&gt;4 wildcard (?) symbols randomly placed — never adjacent or on outer edge&lt;/li&gt;
      &lt;li&gt;Players take turns (Player = O, AI = X)&lt;/li&gt;
      &lt;li&gt;Wildcards count as either O or X for scoring&lt;/li&gt;
      &lt;li&gt;Scoring:
        &lt;ul&gt;
          &lt;li&gt;3 in a row = 1 point&lt;/li&gt;
          &lt;li&gt;4 in a row = 2 points&lt;/li&gt;
          &lt;li&gt;5 in a row = 3 points&lt;/li&gt;
          &lt;li&gt;6 in a row = Instant win&lt;/li&gt;
        &lt;/ul&gt;
      &lt;/li&gt;
      &lt;li&gt;Game ends when no empty cells or no further scoring possible&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
  &lt;script&gt;
    // JavaScript content will be reinserted in next step
  &lt;/script&gt;

&lt;script&gt;
const boardSize = 6;
let board, usedCombos, currentPlayer = &quot;O&quot;, gameOver = false;
let scores = { O: 0, X: 0 };
const logDiv = document.getElementById(&quot;log&quot;);

function resetGame() {
  board = Array.from({ length: boardSize }, () =&gt; Array(boardSize).fill(&quot;&quot;));
  usedCombos = new Set();
  scores = { O: 0, X: 0 };
  currentPlayer = &quot;O&quot;;
  gameOver = false;
  document.getElementById(&quot;playerScore&quot;).textContent = &quot;0&quot;;
  document.getElementById(&quot;aiScore&quot;).textContent = &quot;0&quot;;
  document.getElementById(&quot;gameStatus&quot;).textContent = &quot;&quot;;
  logDiv.innerHTML = &quot;&quot;;
  renderBoard();
  placeWildcards();
}

function renderBoard() {
  const boardDiv = document.getElementById(&quot;board&quot;);
  boardDiv.innerHTML = &quot;&quot;;
  for (let r = 0; r &lt; boardSize; r++) {
    for (let c = 0; c &lt; boardSize; c++) {
      const cell = document.createElement(&quot;div&quot;);
      cell.className = &quot;cell&quot;;
      if (board[r][c] === &quot;?&quot;) cell.classList.add(&quot;wildcard&quot;);
      cell.textContent = board[r][c];
      if (!gameOver &amp;&amp; board[r][c] === &quot;&quot;) {
        cell.onclick = () =&gt; playerMove(r, c);
      }
      boardDiv.appendChild(cell);
    }
  }
}

function placeWildcards() {
  const edges = [0, boardSize - 1];
  let placed = 0;
  while (placed &lt; 4) {
    const r = Math.floor(Math.random() * (boardSize - 2)) + 1;
    const c = Math.floor(Math.random() * (boardSize - 2)) + 1;
    if (board[r][c] === &quot;&quot; &amp;&amp; !hasAdjacentWildcard(r, c)) {
      board[r][c] = &quot;?&quot;;
      placed++;
    }
  }
  renderBoard();
}

function hasAdjacentWildcard(r, c) {
  for (let dr = -1; dr &lt;= 1; dr++) {
    for (let dc = -1; dc &lt;= 1; dc++) {
      if (dr === 0 &amp;&amp; dc === 0) continue;
      const nr = r + dr, nc = c + dc;
      if (nr &gt;= 0 &amp;&amp; nr &lt; boardSize &amp;&amp; nc &gt;= 0 &amp;&amp; nc &lt; boardSize &amp;&amp; board[nr][nc] === &quot;?&quot;) return true;
    }
  }
  return false;
}

function playerMove(r, c) {
  if (board[r][c] !== &quot;&quot;) return;
  board[r][c] = &quot;O&quot;;
  renderBoard();
  checkScore();
  if (!gameOver) setTimeout(aiMove, 300);
}

function aiMove() {
  const style = document.getElementById(&quot;aiStyle&quot;).value;
  const difficulty = document.getElementById(&quot;difficulty&quot;).value;
  const emptyCells = [];
  for (let r = 0; r &lt; boardSize; r++) {
    for (let c = 0; c &lt; boardSize; c++) {
      if (board[r][c] === &quot;&quot;) emptyCells.push([r, c]);
    }
  }

  let move = findStrategicMove(style, difficulty);
  if (!move) move = emptyCells[Math.floor(Math.random() * emptyCells.length)];
  if (move) {
    const [r, c] = move;
    board[r][c] = &quot;X&quot;;
    logDiv.innerHTML += `&lt;div&gt;AI places X at (${r+1}, ${c+1})&lt;/div&gt;`;
    renderBoard();
    checkScore();
  }
}

function findStrategicMove(style, difficulty) {
  const scores = { easy: [0.7, 0.3, 0.1], normal: [0.95, 0.7, 0.3], hard: [1, 0.9, 0.5] }[difficulty];
  const priorities = [6, 5, 4];
  for (let p of priorities) {
    const block = getThreatMove(&quot;O&quot;, p);
    if (block &amp;&amp; Math.random() &lt; scores[6 - p]) return block;
  }
  if (style !== &quot;defensive&quot;) {
    for (let p of priorities) {
      const build = getThreatMove(&quot;X&quot;, p);
      if (build) return build;
    }
  }
  return null;
}

function getThreatMove(symbol, length) {
  const dirs = [[0,1], [1,0], [1,1], [-1,1]];
  for (let r = 0; r &lt; boardSize; r++) {
    for (let c = 0; c &lt; boardSize; c++) {
      for (let [dr, dc] of dirs) {
        let count = 0, wild = 0, empty = null;
        for (let i = 0; i &lt; length; i++) {
          const nr = r + dr*i, nc = c + dc*i;
          if (nr &lt; 0 || nr &gt;= boardSize || nc &lt; 0 || nc &gt;= boardSize) break;
          const val = board[nr][nc];
          if (val === symbol) count++;
          else if (val === &quot;?&quot;) wild++;
          else if (val === &quot;&quot;) empty = [nr, nc];
          else break;
        }
        if (count + wild === length - 1 &amp;&amp; empty) return empty;
      }
    }
  }
  return null;
}

function checkScore() {
  const dirs = [[0,1],[1,0],[1,1],[-1,1]];
  for (let r = 0; r &lt; boardSize; r++) {
    for (let c = 0; c &lt; boardSize; c++) {
      for (let [dr, dc] of dirs) {
        for (let len = 6; len &gt;= 3; len--) {
          const combo = [];
          let countO = 0, countX = 0, wild = 0;
          for (let i = 0; i &lt; len; i++) {
            const nr = r + dr*i, nc = c + dc*i;
            if (nr &lt; 0 || nr &gt;= boardSize || nc &lt; 0 || nc &gt;= boardSize) break;
            combo.push([nr, nc]);
            const val = board[nr][nc];
            if (val === &quot;O&quot;) countO++;
            else if (val === &quot;X&quot;) countX++;
            else if (val === &quot;?&quot;) wild++;
            else break;
          }
          if (combo.length === len) {
            const key = combo.map(([x,y])=&gt;`${x},${y}`).join(&quot;;&quot;);
            if (!usedCombos.has(key)) {
              if (countO &gt; 0 &amp;&amp; countX === 0 &amp;&amp; countO + wild === len) {
                if (len === 6) return endGame(&quot;Player wins with 6 in a row!&quot;);
                scores.O += len - 2;
                usedCombos.add(key);
              }
              else if (countX &gt; 0 &amp;&amp; countO === 0 &amp;&amp; countX + wild === len) {
                if (len === 6) return endGame(&quot;AI wins with 6 in a row!&quot;);
                scores.X += len - 2;
                usedCombos.add(key);
              }
            }
          }
        }
      }
    }
  }
  updateScores();
  if (board.flat().every(cell =&gt; cell !== &quot;&quot;)) endGame(&quot;Game over — no more moves.&quot;);
}

function updateScores() {
  document.getElementById(&quot;playerScore&quot;).textContent = scores.O;
  document.getElementById(&quot;aiScore&quot;).textContent = scores.X;
}

function endGame(msg) {
  gameOver = true;
  document.getElementById(&quot;gameStatus&quot;).textContent = msg;
}

resetGame();
&lt;/script&gt;

&lt;/body&gt;
&lt;/html&gt;

&lt;p&gt;&amp;nbsp;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;span style=&quot;font-size: x-large;&quot;&gt;Development Notes&lt;/span&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;Well, this was a weird one in terms of development, as I seemed to be constantly battling Chat GPT missing out new bits, or putting in the new bits but omitting what was already there. I&#39;m still not quite sure whether it was because I didn&#39;t provide enough clarity in my prompts at times (I didn&#39;t think that was the case, but certainly &lt;i&gt;&lt;b&gt;something &lt;/b&gt;&lt;/i&gt;was going wrong!). I repeatedly hit the free data analysis limits and had to wait till the next day. What I present here is fully playable and incorporates all my game design ideas, although the AI opponent is pretty lacking in any meaningful challenge still.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;For this game I took the classic Noughts and Crosses (Tic-Tac-Toe) idea and quadrupled the playing area - to a 6 x 6 grid - and allowed for runs of 3, 4, 5 and 6 in a row - the last one being an outright game winner, although technically it should be pretty much impossible for it to happen unless one player is &lt;b&gt;&lt;i&gt;really &lt;/i&gt;&lt;/b&gt;not paying attention! Then I added a scoring system for the runs, obviously longer runs bringing higher scores, and also added 4 randomly-placed wildcard symbols (&quot;?&quot;) which could work as either O or X, depending on what was next to it. That was probably the trickiest aspect for the AI to understand and build working code for.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;With the slightly hit-and-miss results from asking the GPT to update the code, there are a couple of things missing from this build. (It was like a game in itself, where version control had random elements!) I did have it highlighting the scoring phases briefly but this got lost from the &quot;final&quot; code. I may possibly go back to it and re-implement this, and improve the AI, but for now it&#39;s a playable enough game so I&#39;m going to leave it alone for now. May be for my next project, I&#39;ll try creating a game that doesn&#39;t involve an opponent - I wonder if this will be easier.&amp;nbsp;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;We shall see!!!&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;See also my first game experiment - just standard &lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-simple-javascript.html&quot;&gt;Noughts and Crosses&lt;/a&gt;.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;b&gt;&lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html&quot;&gt;More about my experiment to make simple JavaScript games with AI Tools&lt;/a&gt;&lt;/b&gt;.&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/4248700504005135602/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-and-wildcards-oh-my.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4248700504005135602'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4248700504005135602'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-and-wildcards-oh-my.html' title='Noughts and Crosses and Wildcards, Oh My! - a JavaScript game created using AI tools'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-748341101519330456</id><published>2025-07-14T12:55:00.000-07:00</published><updated>2025-07-15T08:12:52.348-07:00</updated><title type='text'>Development Updates</title><content type='html'>&lt;p style=&quot;text-align: justify;&quot;&gt;First and foremost, &lt;b&gt;Captain Disaster and the Two Worlds of Riskara&lt;/b&gt; is slowly nearing completion. I received a new character&#39;s voicing today, so will process that and add to the game. What&#39;s needed now is the final voicing, including some by myself, and a bit of work on the codebase, before a release candidate can be sent to the testers. No specific timeframe as yet, but it&#39;s getting nearer. If you haven&#39;t already, please remember to &lt;b&gt;&lt;i&gt;&lt;a href=&quot;https://store.steampowered.com/app/2242270/Captain_Disaster_and_The_Two_Worlds_of_Riskara/&quot; target=&quot;_blank&quot;&gt;wishlist the game&lt;/a&gt;&lt;/i&gt;&lt;/b&gt;!&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;In the background I&#39;ve been making good progress on an update for &lt;b&gt;Flow Problem&lt;/b&gt; - I&#39;ve added music and sound effects, improved the tutorial, added a couple of QOL improvements and most significantly, greatly optimised the code to enhance performance. Pixel-based commands in AGS are pretty expensive and reviewing the original code (which admittedly I&#39;d rather put together in a hurry) the frame rate was pretty bad on anything but a high-end machine (crazy I know for a 320x200 game with extremely simple graphics!) - but this is now vastly improved. I&#39;m having a weird issue with the executable created by the AGS Editor right now, otherwise I might have pushed it out this evening.&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;Finally my experiments with creating JavaScript games with AI have been... frustrating. The initial prototype was created very quickly and looked promising, but since then I&#39;ve had a lot of issues with ChatGPT seemingly not retaining old information when adding new, or asking if I want it to embed the new code into the full page code, me saying yes, it saying that&#39;s done, me looking at the code or testing the game and realising that no, it isn&#39;t... or, it has been, but other crucial parts of the code have been removed. I&#39;m not quite sure if it&#39;s something I&#39;m doing or not. Because of my insistence with this experiment to use only the free version of ChatGPT, I&#39;m frequently having to wait until the next day to get to the point I thought I&#39;d reached 3 prompts earlier. It&#39;s... strange. I may try Gemini for this at some point. Anyway it&#39;s very much a side project, I&#39;m not going to lose any sleep over it.&lt;/p&gt;&lt;p&gt;&lt;/p&gt;&lt;p style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/748341101519330456/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/development-updates_14.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/748341101519330456'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/748341101519330456'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/development-updates_14.html' title='Development Updates'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-7972183550033891169</id><published>2025-07-09T06:27:00.000-07:00</published><updated>2025-07-15T08:10:03.017-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="chat gpt"/><category scheme="http://www.blogger.com/atom/ns#" term="free online game"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><category scheme="http://www.blogger.com/atom/ns#" term="noughts and crosses"/><title type='text'>Noughts and Crosses - simple JavaScript game created with AI</title><content type='html'>&lt;div id=&quot;game&quot; style=&quot;font-family: sans-serif; margin: 20px auto; max-width: 240px;&quot;&gt;
  &lt;h3 id=&quot;status&quot;&gt;Your turn (X)&lt;/h3&gt;

  &lt;label&gt;Difficulty:&lt;/label&gt;
  &lt;select id=&quot;difficulty&quot; onchange=&quot;setDifficulty()&quot;&gt;
    &lt;option value=&quot;50&quot;&gt;Easy&lt;/option&gt;
    &lt;option selected=&quot;&quot; value=&quot;70&quot;&gt;Medium&lt;/option&gt;
    &lt;option value=&quot;90&quot;&gt;Hard&lt;/option&gt;
    &lt;option value=&quot;999&quot;&gt;Unbeatable&lt;/option&gt;
  &lt;/select&gt;

  &lt;br /&gt;&lt;br /&gt;

  &lt;label&gt;Who goes first:&lt;/label&gt;
  &lt;select id=&quot;firstPlayer&quot; onchange=&quot;resetGame()&quot;&gt;
    &lt;option selected=&quot;&quot; value=&quot;human&quot;&gt;Player&lt;/option&gt;
    &lt;option value=&quot;ai&quot;&gt;AI&lt;/option&gt;
  &lt;/select&gt;

  &lt;table style=&quot;border-collapse: collapse; margin-top: 10px; width: 100%;&quot;&gt;
    &lt;tbody&gt;
      &lt;tr&gt;
        &lt;td id=&quot;cell0&quot; onclick=&quot;playerMove(0)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell1&quot; onclick=&quot;playerMove(1)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell2&quot; onclick=&quot;playerMove(2)&quot;&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td id=&quot;cell3&quot; onclick=&quot;playerMove(3)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell4&quot; onclick=&quot;playerMove(4)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell5&quot; onclick=&quot;playerMove(5)&quot;&gt;&lt;/td&gt;
      &lt;/tr&gt;
      &lt;tr&gt;
        &lt;td id=&quot;cell6&quot; onclick=&quot;playerMove(6)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell7&quot; onclick=&quot;playerMove(7)&quot;&gt;&lt;/td&gt;
        &lt;td id=&quot;cell8&quot; onclick=&quot;playerMove(8)&quot;&gt;&lt;/td&gt;
      &lt;/tr&gt;
    &lt;/tbody&gt;
  &lt;/table&gt;

  &lt;button onclick=&quot;resetGame()&quot; style=&quot;margin-top: 10px;&quot;&gt;Restart Game&lt;/button&gt;
&lt;/div&gt;

&lt;script&gt;
  let board = Array(9).fill(&quot;&quot;);
  let gameActive = true;
  const human = &quot;X&quot;;
  const ai = &quot;O&quot;;
  let difficultyPercent = 70;

  const winLines = [
    [0,1,2],[3,4,5],[6,7,8],
    [0,3,6],[1,4,7],[2,5,8],
    [0,4,8],[2,4,6]
  ];

  function setDifficulty() {
    difficultyPercent = parseInt(document.getElementById(&quot;difficulty&quot;).value);
  }

  function playerMove(index) {
    if (!gameActive || board[index] !== &quot;&quot;) return;

    makeMove(index, human);
    if (checkGameEnd(human)) return;

    setTimeout(aiMove, 300);
  }

  function aiMove() {
    if (!gameActive) return;

    let move;
    if (difficultyPercent === 999) {
      move = minimax(board, ai).index;
    } else {
      let blockMove = findCriticalMove(human);
      if (blockMove !== null &amp;&amp; Math.random() * 100 &lt; difficultyPercent) {
        move = blockMove;
      } else {
        let winMove = findCriticalMove(ai);
        if (winMove !== null &amp;&amp; Math.random() * 100 &lt; difficultyPercent) {
          move = winMove;
        } else {
          let empty = board.map((v, i) =&gt; v === &quot;&quot; ? i : null).filter(i =&gt; i !== null);
          move = empty[Math.floor(Math.random() * empty.length)];
        }
      }
    }

    makeMove(move, ai);
    checkGameEnd(ai);
  }

  function findCriticalMove(player) {
    for (let line of winLines) {
      const [a, b, c] = line;
      const values = [board[a], board[b], board[c]];
      const count = values.filter(v =&gt; v === player).length;
      const emptyCount = values.filter(v =&gt; v === &quot;&quot;).length;

      if (count === 2 &amp;&amp; emptyCount === 1) {
        if (board[a] === &quot;&quot;) return a;
        if (board[b] === &quot;&quot;) return b;
        if (board[c] === &quot;&quot;) return c;
      }
    }
    return null;
  }

  function makeMove(index, player) {
    board[index] = player;
    document.getElementById(&quot;cell&quot; + index).innerText = player;
  }

  function checkGameEnd(currentPlayer) {
    const hasWon = winLines.some(line =&gt; {
      const [a, b, c] = line;
      return board[a] === currentPlayer &amp;&amp; board[b] === currentPlayer &amp;&amp; board[c] === currentPlayer;
    });

    if (hasWon) {
      gameActive = false;
      setTimeout(() =&gt; {
        alert(currentPlayer === human ? &quot;You win!&quot; : &quot;AI wins!&quot;);
      }, 100);
      return true;
    }

    if (board.every(cell =&gt; cell !== &quot;&quot;)) {
      gameActive = false;
      setTimeout(() =&gt; {
        if (difficultyPercent === 999) {
          alert(&quot;A strange game. The only winning move is not to play.&quot;);
        } else {
          alert(&quot;It&#39;s a draw!&quot;);
        }
      }, 100);
      return true;
    }

    return false;
  }

  function resetGame() {
    board = Array(9).fill(&quot;&quot;);
    gameActive = true;

    for (let i = 0; i &lt; 9; i++) {
      document.getElementById(&quot;cell&quot; + i).innerText = &quot;&quot;;
    }

    setDifficulty();
    const firstPlayer = document.getElementById(&quot;firstPlayer&quot;).value;

    if (firstPlayer === &quot;ai&quot;) {
      document.getElementById(&quot;status&quot;).innerText = &quot;AI&#39;s turn...&quot;;
      setTimeout(aiMove, 300);
    } else {
      document.getElementById(&quot;status&quot;).innerText = &quot;Your turn (X)&quot;;
    }
  }

  function minimax(newBoard, player) {
    const availSpots = newBoard.map((v, i) =&gt; v === &quot;&quot; ? i : null).filter(i =&gt; i !== null);

    if (checkWinner(newBoard, human)) return { score: -10 };
    if (checkWinner(newBoard, ai)) return { score: 10 };
    if (availSpots.length === 0) return { score: 0 };

    let moves = [];

    for (let i = 0; i &lt; availSpots.length; i++) {
      let move = {};
      move.index = availSpots[i];
      newBoard[availSpots[i]] = player;

      if (player === ai) {
        let result = minimax(newBoard, human);
        move.score = result.score;
      } else {
        let result = minimax(newBoard, ai);
        move.score = result.score;
      }

      newBoard[availSpots[i]] = &quot;&quot;;
      moves.push(move);
    }

    let bestMove;
    if (player === ai) {
      let bestScore = -Infinity;
      for (let i = 0; i &lt; moves.length; i++) {
        if (moves[i].score &gt; bestScore) {
          bestScore = moves[i].score;
          bestMove = i;
        }
      }
    } else {
      let bestScore = Infinity;
      for (let i = 0; i &lt; moves.length; i++) {
        if (moves[i].score &lt; bestScore) {
          bestScore = moves[i].score;
          bestMove = i;
        }
      }
    }

    return moves[bestMove];
  }

  function checkWinner(b, player) {
    return winLines.some(line =&gt; {
      const [a, b1, c] = line;
      return b[a] === player &amp;&amp; b[b1] === player &amp;&amp; b[c] === player;
    });
  }

  // Initial setup
  window.onload = resetGame;
&lt;/script&gt;

&lt;style&gt;
  td {
    border: 1px solid #333;
    width: 60px;
    height: 60px;
    text-align: center;
    vertical-align: middle;
    font-size: 2em;
    cursor: pointer;
  }
  td:hover {
    background-color: #f0f0f0;
  }
&lt;/style&gt;
&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;h1 style=&quot;text-align: left;&quot;&gt;Development Notes&lt;/h1&gt;&lt;p&gt;&lt;/p&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: left;&quot;&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;Gotta start small, right? So I started with the smallest game project I could think of - noughts and crosses, or &quot;Tic-Tac-Toe&quot;, if you prefer. Since the visuals are extremely basic and the game logic is about as simple as it gets, not to mention that AI knew the rules without me having to tell it, getting an initial mockup was ridiculously easy. What took a little more effort was fine-tuning the AI opponent - even though (apart from the &quot;unbeatable&quot; level - draw on that to see a quote from War Games, an 80s movie I love) , it was basically a case of combining randomn placement and probability weighting to make the best moves (obviously, the easier the opponent, the lower the probability of it choosing the best move).&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;Then things like adding choices (difficulty level, who goes first) and how the game responds to game-ending events. It&#39;s very simple, but quite playable. I was glad I started with this as it was easy but also very informative as to how to structure the next, more ambitious project.&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;This game took 8 prompt iterations to reach the final stage. With prompting, testing and thinking time, I guess it took about an hour to put together. There is scope to expand it but I&#39;m not really planning to (although I may possible get it updated to work better on mobile devices) as this was absolutely just a learning project and the real fun will be trying to create a game with a unique ruleset.&amp;nbsp;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;br /&gt;&lt;/div&gt;&lt;div style=&quot;text-align: justify;&quot;&gt;&lt;b style=&quot;text-align: left;&quot;&gt;&lt;a href=&quot;http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html&quot;&gt;More about my experiment to make simple JavaScript games with AI Tools&lt;/a&gt;&lt;/b&gt;&lt;span style=&quot;text-align: left;&quot;&gt;.&lt;/span&gt;&lt;/div&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/7972183550033891169/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-simple-javascript.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7972183550033891169'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/7972183550033891169'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/noughts-and-crosses-simple-javascript.html' title='Noughts and Crosses - simple JavaScript game created with AI'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-3815364547006905015</id><published>2025-07-09T01:53:00.000-07:00</published><updated>2026-03-08T01:29:59.929-08:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="artificial intelligance"/><category scheme="http://www.blogger.com/atom/ns#" term="chat gpt"/><category scheme="http://www.blogger.com/atom/ns#" term="css"/><category scheme="http://www.blogger.com/atom/ns#" term="experiment"/><category scheme="http://www.blogger.com/atom/ns#" term="html"/><category scheme="http://www.blogger.com/atom/ns#" term="javascript"/><title type='text'>About my experiment to make simple JavaScript games using AI tools</title><content type='html'>&lt;p&gt;&amp;nbsp;In case anyone’s thinking that using AI tools to help create
games is just a case of typing into ChatGPT “make me a game like Tetris, but
better”, it &lt;b&gt;isn’t&lt;/b&gt;. (Well, I guess in truth I haven’t actually tried
that, but…) The thing with AI is that the quality of output you get is very much
dependent on the quality of input you give it. Also, don’t expect that you can
get what you’re looking for on the first go – you can try to front-load the
first prompt with context and information, but that will still only get you so
far.&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;So why did I want to experiment with using AI to make simple
JavaScript games in the first place?&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpFirst&quot; style=&quot;mso-list: l0 level1 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: Symbol; mso-bidi-font-family: Symbol; mso-fareast-font-family: Symbol;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;·&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;I have no interest in actually learning to code
JavaScript – I simply don’t have the time. It took me long enough to learn AGS
script (which is very similar to C++) which means I can usually follow what’s
happening in JS due to similarities in the two languages structures, but I can’t
code it. (I did code HTML many years ago, but really even back a couple of decades
ago, most people were using a visual editor rather than hand-coding it anyway.)&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;mso-list: l0 level1 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: Symbol; mso-bidi-font-family: Symbol; mso-fareast-font-family: Symbol;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;·&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;I use AI quite extensively in my day job and
have become fascinated by the possibilities. Like most people who have actually
spent significant time learning about and using AI, I see it as simply a tool
that can be used, and rather than deskilling it can be used to help people in
certain professions focus on what their jobs actually should be, rather than spending
most of their time on repetitive documentation tasks. I do appreciate that
there are specific use cases where use of AI is highly controversial – art generation
for instance – and I fully understand that viewpoint. &lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;mso-list: l0 level1 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: Symbol; mso-bidi-font-family: Symbol; mso-fareast-font-family: Symbol;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;·&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Linked to that, it seems to me that AI has been
trained on coding that has been intentionally made publicly available. My goal
is to leverage the strengths of AI – which is in producing clean, usable
boilerplate code and implementing simple game logic – while avoiding trying to
get it to do anything too complex. That’s where a real coder is still (from my understanding,
at least) miles ahead of what a GPT can do. &lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;mso-list: l0 level1 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: Symbol; mso-bidi-font-family: Symbol; mso-fareast-font-family: Symbol;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;·&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Given that, I’m using an AGILE methodology – my process
is a very simple three-tiered loop:&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;margin-left: 72pt; mso-add-space: auto; mso-list: l0 level2 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;; mso-fareast-font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;o&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Think about what I want to achieve on this
iteration&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;margin-left: 72pt; mso-add-space: auto; mso-list: l0 level2 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;; mso-fareast-font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;o&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Create a prompt that explains this to the GPT as
clearly as possible&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpMiddle&quot; style=&quot;margin-left: 72pt; mso-add-space: auto; mso-list: l0 level2 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;; mso-fareast-font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;o&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;Test the results, and go on to the next
iteration (unless I feel the game has already reached its target state)&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoListParagraphCxSpLast&quot; style=&quot;margin-left: 72pt; mso-add-space: auto; mso-list: l0 level2 lfo1; text-indent: -18pt;&quot;&gt;&lt;!--[if !supportLists]--&gt;&lt;span style=&quot;font-family: &amp;quot;Courier New&amp;quot;; mso-fareast-font-family: &amp;quot;Courier New&amp;quot;;&quot;&gt;&lt;span style=&quot;mso-list: Ignore;&quot;&gt;o&lt;span style=&quot;font: 7pt &amp;quot;Times New Roman&amp;quot;;&quot;&gt;&amp;nbsp;&amp;nbsp;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;!--[endif]--&gt;This is very small-scale stuff, just me working
with AI, so I’m not bothering with even thinking of anything in terms of “sprints”
etc.&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;What’s required from me in this case, since I’m not doing any
of the actual coding? Well, first of all, I need the original idea. (My very
first project was a simple noughts-and-crosses game, just as proof of concept,
but my plan is to create some small but unique games.) Then it’s a case of
knowing how to write high-quality AI prompts, and how to give it comprehensive,
clear instructions that are not open to AI hallucinations. Along with this,
understanding how the game should work at a fundamental level is required – if you’re
just remaking noughts and crosses then yeah, no specific knowledge is needed,
but if you want to create something unique, you need to first understand yourself
exactly how it should work “under the hood” and then how to get that across to
the GPT.&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;In theory, each iteration should take you nearer to the goal.
In practice, I have found that whilst this is usually the case if my prompts
are very precise, there is still room for the GPT to misinterpret what I’ve
asked it to do (the blame for this can be shared between me and the GPT!)&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;&lt;o:p&gt;&amp;nbsp;&lt;/o:p&gt;I’ve set certain limitations for myself – I only want to
create something in vanilla JavaScript (with a little HTML / CSS thrown in for
the page design), so no libraries allowed, it must be either a single player or
player vs AI opponent, and it must run within a Blogger post (so I can put it
here along with a short explanation of how the game was designed). I will also
be using only the free Chat-GPT model, and (potentially barring some very small
modifications to the HTML components) not directly editing the code myself.&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;My goals with this are simply to have a bit of fun seeing what
I can come up with and learn a bit more about the possibilities and limitations
of current free GPT models. I also needed something a little different to
kick-start my brain – with what time I have available, hand-crafting games in
AGS is still my main focus. When all’s said and done, there’s nothing quite
like solving a tricky coding problem yourself!&lt;o:p&gt;&lt;/o:p&gt;&lt;/p&gt;

&lt;p class=&quot;MsoNormal&quot;&gt;Another small AI-adjacent experiment I’ve put together is a browser-based &lt;strong data-end=&quot;855&quot; data-start=&quot;837&quot;&gt;&lt;a href=&quot;https://elizaemulator.com/&quot; target=&quot;_blank&quot;&gt;Eliza Emulator&lt;/a&gt;&lt;/strong&gt;, inspired by the classic 1960s chatbot.”&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/3815364547006905015/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/3815364547006905015'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/3815364547006905015'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/about-my-experiment-to-make-simple.html' title='About my experiment to make simple JavaScript games using AI tools'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-4735721282486721210</id><published>2025-07-08T11:04:00.000-07:00</published><updated>2025-07-08T11:04:53.287-07:00</updated><category scheme="http://www.blogger.com/atom/ns#" term="AI"/><category scheme="http://www.blogger.com/atom/ns#" term="captain disaster"/><category scheme="http://www.blogger.com/atom/ns#" term="flow problem"/><category scheme="http://www.blogger.com/atom/ns#" term="gamedev"/><category scheme="http://www.blogger.com/atom/ns#" term="two worlds of riskara"/><title type='text'>Development Updates</title><content type='html'>Progress on &lt;b&gt;&lt;a href=&quot;https://store.steampowered.com/app/2242270/Captain_Disaster_and_The_Two_Worlds_of_Riskara/&quot; target=&quot;_blank&quot;&gt;Captain Disaster and the Two Worlds of Riskara&lt;/a&gt;&lt;/b&gt; has been slowly progressing. I&#39;ve had a struggle with certain elements of it and a few setbacks, but it&#39;s still getting ever closer to a release candidate for the final big round of testing.&amp;nbsp;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Unfortunately, I have the type of brain that really struggles to stay on one project at a time - sometimes this is helpful, sometimes not, and I&#39;m sure a few people reading this will be wondering what on earth I&#39;m talking about, whilst others will be nodding sympathetically because that&#39;s how their brain works too. Anyway, the long and short of it is that I&#39;ve gone back to &lt;b&gt;&lt;a href=&quot;https://captaind.itch.io/flow-problem&quot; target=&quot;_blank&quot;&gt;Flow Problem&lt;/a&gt;&lt;/b&gt;, done a heap of code optimisation and added some music and sound effects, and will probably release a big update to that game soon.&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;Last but potentially not least, I&#39;ve decided to investigate the feasibility of using AI to create simple JavaScript games. I&#39;ll post a bit more on this later. I&#39;m only doing it for fun, to see if I can create some quick fun puzzle games that you can play free in a browser, but my early experiments are very possible. It does make me think of the joke &quot;Make my game&quot; button older AGS editors used to have, but that&#39;s not how it works in real life! I&#39;ll do a post on my methodology soon.&amp;nbsp;&lt;/div&gt;&lt;div&gt;&lt;br /&gt;&lt;/div&gt;&lt;div&gt;&lt;i&gt;(I&#39;m not planning on using AI to make anything in AGS btw!!! Just in case anyone wondered.)&amp;nbsp;&lt;/i&gt;&lt;/div&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/4735721282486721210/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/development-updates.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4735721282486721210'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/4735721282486721210'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/development-updates.html' title='Development Updates'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-6638327113696044542</id><published>2025-07-03T09:06:00.000-07:00</published><updated>2025-07-03T09:06:14.872-07:00</updated><title type='text'>Steam Summer Sale!</title><content type='html'>&lt;p&gt;&amp;nbsp;It&#39;s that time again - the weather causes prices to melt, or something.&lt;/p&gt;&lt;p&gt;Anyway, all three of my games on Steam are a massive 75% off for the duration of the sale:&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;
&lt;iframe src=&quot;https://store.steampowered.com/widget/1298280/&quot; frameborder=&quot;0&quot; width=&quot;646&quot; height=&quot;190&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;/p&gt;
&lt;iframe src=&quot;https://store.steampowered.com/widget/2097090/&quot; frameborder=&quot;0&quot; width=&quot;646&quot; height=&quot;190&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;/p&gt;
&lt;iframe src=&quot;https://store.steampowered.com/widget/1285960/&quot; frameborder=&quot;0&quot; width=&quot;646&quot; height=&quot;190&quot;&gt;&lt;/iframe&gt;
&lt;p&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/6638327113696044542/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/steam-summer-sale.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6638327113696044542'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/6638327113696044542'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/07/steam-summer-sale.html' title='Steam Summer Sale!'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry><entry><id>tag:blogger.com,1999:blog-5196359578514433536.post-8270618924936337275</id><published>2025-02-23T02:42:00.000-08:00</published><updated>2025-02-23T02:42:43.363-08:00</updated><title type='text'>Captain Disaster and the Two Worlds of Riskara - in 15 seconds!</title><content type='html'>&lt;p&gt;Well, this &quot;My indie game in 15 seconds&quot; thing has been trending on social media for a while now, so I thought I&#39;d better get in on the act - then I also realised it&#39;s been ages since I updated this blog!&lt;/p&gt;&lt;p&gt;So here we are:&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;div class=&quot;separator&quot; style=&quot;clear: both; text-align: center;&quot;&gt;&lt;iframe allowfullscreen=&#39;allowfullscreen&#39; webkitallowfullscreen=&#39;webkitallowfullscreen&#39; mozallowfullscreen=&#39;mozallowfullscreen&#39; width=&#39;320&#39; height=&#39;266&#39; src=&#39;https://www.blogger.com/video.g?token=AD6v5dyMYudNQTzcxBZdKHf0mxPgxRIOhejqotB3O17aGSRiHrn-LZIet-aZ-3bsx4cOxprcdpl9K6_7mIatbRyWhg&#39; class=&#39;b-hbp-video b-uploaded&#39; frameborder=&#39;0&#39;&gt;&lt;/iframe&gt;&lt;/div&gt;&lt;br /&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;&lt;p&gt;&lt;br /&gt;&lt;/p&gt;</content><link rel='replies' type='application/atom+xml' href='http://www.captaindisasterthecomputergame.com/feeds/8270618924936337275/comments/default' title='Post Comments'/><link rel='replies' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/02/captain-disaster-and-two-worlds-of.html#comment-form' title='0 Comments'/><link rel='edit' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/8270618924936337275'/><link rel='self' type='application/atom+xml' href='http://www.blogger.com/feeds/5196359578514433536/posts/default/8270618924936337275'/><link rel='alternate' type='text/html' href='http://www.captaindisasterthecomputergame.com/2025/02/captain-disaster-and-two-worlds-of.html' title='Captain Disaster and the Two Worlds of Riskara - in 15 seconds!'/><author><name>CaptainD</name><uri>http://www.blogger.com/profile/06905733791674065875</uri><email>noreply@blogger.com</email><gd:image rel='http://schemas.google.com/g/2005#thumbnail' width='16' height='16' src='https://img1.blogblog.com/img/b16-rounded.gif'/></author><thr:total>0</thr:total></entry></feed>