<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>techai &#8211; Tech AI Connect</title>
	<atom:link href="https://techaiconnect.com/author/techai/feed/" rel="self" type="application/rss+xml" />
	<link>https://techaiconnect.com</link>
	<description>All Tek Information for You</description>
	<lastBuildDate>Sun, 06 Jul 2025 08:34:12 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.8.2</generator>
	<item>
		<title>New Shindo Life Codes for June 2025: Get Free RELL Coins and Spins</title>
		<link>https://techaiconnect.com/new-shindo-life-codes-for-june-2025-get-free-rell-coins-and-spins/</link>
					<comments>https://techaiconnect.com/new-shindo-life-codes-for-june-2025-get-free-rell-coins-and-spins/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Wed, 16 Jul 2025 21:26:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[bonus spins]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[gaming rewards]]></category>
		<category><![CDATA[June 2025 codes]]></category>
		<category><![CDATA[RELL Coins]]></category>
		<category><![CDATA[Roblox]]></category>
		<category><![CDATA[Shindo Life codes]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4232</guid>

					<description><![CDATA[Discover the latest Shindo Life codes updated on June 16, 2025, to get free RELL Coins and bonus spins. Start your journey with an edge using these codes.]]></description>
										<content:encoded><![CDATA[<p>Update: checked for new Shindo Life codes on June 16, 2025</p>
<p>Since I started playing Shinobi Life 2 on Roblox, I&#8217;ve been captivated by the game. With its rebranding to Shindo Life, the experience has only improved. However, becoming the next Hokage in this Naruto-inspired world is challenging. To give you a head start, we&#8217;ve gathered all the active Shindo Life codes that offer freebies like RELL Coins and bonus spins. Use these RELL Coins and Spins for skill or clan upgrades to easily defeat enemies.</p>
<h2>All New Shindo Life Codes</h2>
<ul>
<li><strong>RELLbrothr3n! </strong>(<strong>NEW</strong>)</li>
<li><strong>godd00dupdate! </strong>(<strong>NEW</strong>)</li>
<li><strong>mansUPDATealreadyg0d!</strong> (<strong>NEW</strong>)</li>
<li><strong>updateDEtingMon!</strong> (<strong>NEW</strong>)</li>
<li><strong>rellseasmoviehuh!</strong> (<strong>NEW</strong>)</li>
<li><strong>RellenSparrow!</strong> (<strong>NEW</strong>)</li>
<li><strong>pr3ssdeUPDATEbotton!</strong></li>
<li><strong>nofampressopdatebotton!</strong> </li>
<li><strong>RELLseasmovie37!</strong> </li>
<li><strong>updateshindoOMG! </strong></li>
<li><strong>updateb0ttonfam!</strong> </li>
<li><strong>wenrellseasmatewen!</strong> </li>
<li><strong>marchfam2025!</strong></li>
<li><strong>fampressdeupdatebotton!</strong> </li>
<li><strong>rellseasmove3when!</strong></li>
<li><strong>Apr1lF00lc00l!</strong> </li>
<li><strong>UPopUpd4te!</strong> </li>
<li><strong>RELLtopbrothers!</strong></li>
<li><strong>updopdateop!</strong></li>
<li><strong>justupdgamefam!</strong></li>
<li><strong>DragonScammer! </strong></li>
<li><strong>RELLbestBrothers!</strong> </li>
<li><strong>Merry2024Christmas! </strong></li>
<li><strong>2025RELLmas! </strong></li>
<li><strong>HappyHolidaysfromRELL!</strong> </li>
<li><strong>FixG4me239!</strong> </li>
<li><strong>Gem239!</strong> </li>
<li><strong>2024NovCodes! </strong></li>
<li><strong>RELLbadBirthday! </strong></li>
<li><strong>NovemberVM! </strong></li>
<li><strong>PlannedConcepts! </strong></li>
<li><strong>Co0lConceptsbr0!</strong> </li>
<li><strong>siCkConceptsBr0! </strong></li>
<li><strong>NoVeMmentality!</strong> </li>
<li><strong>S0back2025! </strong></li>
<li><strong>r311SeasBigUps!</strong> </li>
<li><strong>RELLNindonSeas!</strong> </li>
<li><strong>BigManTingsTesting! </strong></li>
<li><strong>NoMoremessingOnlyGrinding! </strong></li>
</ul>
<h3>Expired Shindo Life Codes</h3>
<ul>
<li>AnimatingLikeimSkitty!</li>
<li>RelaxFammity!</li>
<li>FaM2028Aidenn!</li>
<li>AidennGrunt2028!</li>
<li>GruntLifeCodes!</li>
<li>ShindoGruntC0dez!</li>
<li>gruntLifeNindon!</li>
<li>RELLSeasRealSeas!</li>
<li>RellseaCsparrow!</li>
<li>AidenFAM2028!</li>
<li>gr1ndGrindG!</li>
<li>OnlyWworking!</li>
<li>NinD0nMusicFire!</li>
<li>NindonIsPeak!</li>
<li>Nind0nWwWPeak!</li>
<li>R3LLbadmanmanW!</li>
<li>WorkDawgStopSlackng!</li>
<li>PaintinPro!</li>
<li>Nind2nWWPea!</li>
<li>R3LLradmaW!</li>
<li>SaveHairohGod!</li>
<li>CrackAhSlapMan!</li>
<li>JankSwanky!</li>
<li>HairySaviorB0B!</li>
<li>PeterPorker!</li>
<li>hairyId1!</li>
<li>hairyId2!</li>
<li>hairyId3!</li>
<li>hairyId4!</li>
<li>hairyId5!</li>
<li>RELLpeakgrind!</li>
<li>NoStallOnlyWork!</li>
<li>NinD0nTestingb4Seas!</li>
<li>ZbruushGr1nd!</li>
<li>WobawgdeSlackng!</li>
<li>SandFlightNerfYes!</li>
<li>EmberKageisL!</li>
<li>HazeNobuffs!</li>
<li>RELLCSparrow!</li>
<li>PokeshindoMon!</li>
<li>NimbusCarriedBySpecs!</li>
<li>ObelisktakingLs!</li>
<li>OneTappedHimBeastBomb!</li>
<li>RayySpecCarried!</li>
<li>AidennGruntWork!</li>
<li>ImADuneKun!</li>
<li>RELL4NewYears!</li>
<li>GUnChuckduck!</li>
<li>NINd0nUpN3xT!</li>
<li>famskillinnitbruz</li>
<li>sk1llIzzuee!</li>
</ul>
<p>While you are here, check out the Roblox codes for other experiences in our master list. If you&#8217;re looking for more anime-inspired fun, grab the Anime Defenders codes and Type Soul codes to try something new.</p>
<h2>How to Redeem Shindo Life Codes</h2>
<p>Now that you have the Shindo Life codes, follow the steps below to redeem them and claim your rewards:</p>
<ul>
<li>Launch <a href="https://www.roblox.com/games/4616652839/Shindo-Life-237" rel="noopener nofollow" target="_blank">Shindo Life</a> in the Roblox player.</li>
<li>Navigate to the Edit menu to find the code redemption section.</li>
<li>Click the <strong>YOUTUBE CODE</strong> button on the right side.</li>
<li>Paste a working code in the text box to claim your rewards.</li>
</ul>
<figure><img decoding="async" alt="Shindo Life codes redeem section" src="https://techaiconnect.com/wp-content/uploads/2025/06/Shindo-Life-codes-section.jpg"/></figure>
<h2>Where Can You Find More Shindo Life Codes</h2>
<p>We regularly update our active codes list to ensure you never miss out on the latest rewards. Bookmark this page for convenience. You can also follow the <a href="https://x.com/RellGames" rel="noopener nofollow" target="_blank">official Shindo Life page</a> and developer page on X for more codes. These pages occasionally share new codes.</p>
<p>Looking for more announcements and updates? Check out the <a href="https://discord.com/invite/rellgames" rel="noopener nofollow" target="_blank">official Rell Games Discord server</a> and Rell Games YouTube. In the Discord server, you can learn more about Shindo Life and the developer’s upcoming game, Rell Seas.</p>
<h2>Why Are My Shindo Life Codes Not Working?</h2>
<p>If you encounter an <strong>‘invalid error’</strong> while entering codes, it might be due to using an expired code. You will notice nothing happening if you enter an incorrect code. If the code is from our active list, ensure you copy and paste it exactly. Even a space at the end of the code can cause an error. If you&#8217;ve done everything correctly, try restarting the game or wait for the system to be fixed in Shindo Life.</p>
<h2>More Ways To Get Free Spins in Shindo Life</h2>
<p>Have you used all the working Shindo Life codes above and still want more spins? Spins are essential for upgrading your skills in Shindo Life. To get more for free, complete quests, log in daily, and claim daily rewards. You will also earn more spins as level-up rewards at certain levels. If you wish to spend Robux, you can purchase 15 spins for 45 Robux, 30 for 60 Robux, and 60 for 100 Robux.</p>
<p>Have you started your Shindo Life journey with all the freebies? Don&#8217;t forget to check out the Shindo Life bloodlines tier list before choosing your clan.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/new-shindo-life-codes-for-june-2025-get-free-rell-coins-and-spins/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>New Fruit Battlegrounds Codes for June 2025</title>
		<link>https://techaiconnect.com/new-fruit-battlegrounds-codes-for-june-2025/</link>
					<comments>https://techaiconnect.com/new-fruit-battlegrounds-codes-for-june-2025/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Wed, 16 Jul 2025 19:50:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[devil fruits]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[Free gems]]></category>
		<category><![CDATA[Fruit Battlegrounds codes]]></category>
		<category><![CDATA[gaming codes]]></category>
		<category><![CDATA[June 2025 updates]]></category>
		<category><![CDATA[One Piece]]></category>
		<category><![CDATA[Roblox]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4228</guid>

					<description><![CDATA[Discover the latest Fruit Battlegrounds codes updated on June 14, 2025. Use these codes to get free Gems and spin for new devil fruits in this exciting Roblox game inspired by One Piece.]]></description>
										<content:encoded><![CDATA[<p>Update: Added new Fruit Battlegrounds codes on June 14, 2025.</p>
<p>If the grind is getting difficult and you&#8217;re looking to secure free rewards in Fruit Battlegrounds, we&#8217;re here to help. Like many Roblox games inspired by One Piece, this one offers a fun way to experience devil fruits. While you will find yourself buying new devil fruits and battling enemies to level up, there&#8217;s another way to get stronger fruits. Use our list of Fruit Battlegrounds codes to obtain free Gems that allow you to spin for a new devil fruit.</p>
<h2>All New Fruit Battlegrounds Codes</h2>
<ul>
<li><strong>LOL960K!</strong>: 500 Gems (<strong><mark class="has-inline-color has-gradient-red-color">NEW</mark></strong>)</li>
</ul>
<h3>Expired Fruit Battlegrounds Codes</h3>
<ul>
<li>950KOMGGG!</li>
<li>940KHAPPYDAYZ</li>
<li>930KINS4NITY</li>
<li>HYPEE920K!</li>
<li>WOWZER910</li>
<li>OMG9HUNDRED!</li>
<li>MAGNIFICENT890K!!</li>
<li>BIGDAY</li>
<li>ANTICIPATION</li>
<li>IAMLEOPARD</li>
<li>BIGUPDATE20</li>
<li>860KHYPEE</li>
<li>WORLDWAR2025</li>
<li>FREEBOTAGAIN</li>
<li>NEWYEAR2025!!</li>
<li>FREEBOOT!</li>
<li>840MENTAL</li>
<li>WINTER2024!</li>
<li>OMGUPDATE19!</li>
<li>COOKEDPASTRY!</li>
<li>830KBRO!</li>
<li>SPLENDID820</li>
<li>810TIME!</li>
<li>BIG8HUNDO!</li>
<li>SHINE790K</li>
<li>GLITTER780K</li>
<li>4BUNDANCY</li>
<li>OMGREBOOTAGAIN</li>
<li>NANAP0CALYPSE!</li>
<li>770KWOW!</li>
<li>SORRY4DELAY</li>
<li>NEWBOUNTYERA!</li>
<li>HOWLINGFALL!</li>
<li>LVLBUFFHYPE</li>
<li>ICEW0LF</li>
<li>SM4LLFRY</li>
<li>760KISKRAZY</li>
<li>750KINSANE!</li>
<li>HAHA740K!</li>
<li>730EXPLODE</li>
<li>RIGHT720</li>
<li>710KOMG!!</li>
<li>BAM700K</li>
<li>SCORCHINGSUMMER</li>
<li>BIGB1RD</li>
<li>3LAPSED!</li>
<li>UNLOCK690K!</li>
<li>SORRYMOBILE</li>
<li>POW680K!</li>
<li>LETSGOO130K</li>
<li>650ISMADD!</li>
<li>AYO640K!</li>
<li>JEEZ630</li>
<li>WOWZER620!</li>
<li>TRUEMENACE</li>
<li>ITSTIME!!</li>
<li>MOSTH4TED</li>
<li>TRUEMENACE</li>
<li>THXFOR610!!</li>
<li>DAHUNT2024</li>
<li>OMG600K!!!</li>
<li>HIGH590</li>
<li>SPR34DL0V3</li>
<li>VALENTINES2024</li>
<li>POS1T1V1TY</li>
<li>580FLAMES</li>
<li>570FAVS</li>
<li>N3WW0RLD!</li>
<li>ILOV3C4NDY</li>
<li>HYPEWHOLECAKE</li>
<li>LAGFIXX</li>
<li>CLEANREB00T</li>
<li>YOOO560</li>
<li>550POGG</li>
<li>540DAYUM</li>
<li>530GYAT</li>
<li>SHEEESH520!!</li>
<li>WINTAH2023</li>
<li>LAVAPARTY</li>
<li>KOLDKRAZE!</li>
</ul>
<p>While you’re here, we recommend checking out our Roblox codes master list to grab working codes for other popular games. You will find new codes for other games, such as Anime Vanguards, Blox Fruits, and even Anime Fruit World in there.</p>
<h2>How to Redeem Fruit Battlegrounds Codes</h2>
<p>Follow the step-by-step process below to redeem the latest codes for Fruit Battlegrounds:</p>
<ul>
<li>Open <a href="https://www.roblox.com/games/9224601490/LIGHT-V2-RANKED-Fruit-Battlegrounds" rel="noopener nofollow" target="_blank">Fruit Battlegrounds</a> in the Roblox player.</li>
<li>Click the <strong>Spin Fruit option</strong> in the main menu.</li>
<li>Click the<strong> treasure chest</strong> in the middle of the room.</li>
<li>You will find the <strong>Codes section</strong> on the bottom left.</li>
<li>Input an active code and click the <strong>Redeem</strong> button.</li>
</ul>
<figure><img decoding="async" alt="redeem codes for fruit battlegrounds" src="https://techaiconnect.com/wp-content/uploads/2025/06/redeem-codes-for-fruit-battlegrounds.jpg"/></figure>
<p>Additionally, if you want access to better fruits when you spin the wheel, you can always get Roblox premium. You can click the Buy button at the bottom of the screen in the <strong>Redeem section</strong> to get the premium membership. This will <strong>get you a 20% luck boost </strong>in Fruit Battlegrounds.</p>
<h2>How to Get More Codes for Fruit Battlegrounds</h2>
<p>We frequently update all our code lists, so your best bet to access new Fruit Battlegrounds codes is bookmarking our page and revisiting it when an update is rolled out.</p>
<p><strong>Also Read:</strong> How to Get Full Body Haki in Blox Fruits</p>
<p>However, if you are planning to search for the latest codes yourself, follow the <a href="https://discord.com/invite/popo" rel="noopener nofollow" target="_blank">Fruit Battlegrounds Discord server</a>, which is operated by its developer POPO. The codes are usually shared in the “<strong>fb-updates</strong>” channel on Discord.</p>
<h2>What is Fruit Battlegrounds?</h2>
<p>Fruit Battlegrounds is an adventure game inspired by the One Piece animanga series. The primary weapon in Fruit Battlegrounds is <strong>devil fruits</strong>, like the Gum-Gum Fruit used by Luffy. To get these fruits, you must spin the wheel using the gems you get from the codes.</p>
<p>With strong fruits, you can grind the game easily and defeat hordes of enemies quickly. Codes for Fruit Battlegrounds are the ideal way to get gems for free. You can also get more gems by defeating bosses or getting new titles. If you are AFK, you will enter the AFK world where you get free gems depending on your time spent there.</p>
<p>If a code does not work, let us know in the comments below, and we will remove it from our codes list right away. Also, share any codes that we have missed out on in the comments below.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/new-fruit-battlegrounds-codes-for-june-2025/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>All Star Tower Defense Codes: Free Gems and Stardust</title>
		<link>https://techaiconnect.com/all-star-tower-defense-codes-free-gems-and-stardust/</link>
					<comments>https://techaiconnect.com/all-star-tower-defense-codes-free-gems-and-stardust/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Mon, 14 Jul 2025 16:37:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[all star tower defense codes]]></category>
		<category><![CDATA[anime TD]]></category>
		<category><![CDATA[ASTD codes]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[Free gems]]></category>
		<category><![CDATA[game codes]]></category>
		<category><![CDATA[Roblox]]></category>
		<category><![CDATA[stardust]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4220</guid>

					<description><![CDATA[Discover the newest All Star Tower Defense codes to get free gems and stardust. Level up and enhance your anime TD characters collection with these working codes.]]></description>
										<content:encoded><![CDATA[<p>Update: checked for new All Star Tower Defense codes on June 1, 2025</p>
<p>Roblox All Star Tower Defense is one of the original TD games on the platform. To advance and battle the toughest foes, you&#8217;ll need free stardust and gems in All Star Tower Defense. Below is a list of the latest All Star Tower Defense codes that provide free gems and units. Use them to build the ultimate anime TD character collection.</p>
<h2>All New Star Tower Defense Codes</h2>
<h3>Working ASTD Codes</h3>
<ul>
<li><strong>superdaydaykid100ksubs</strong>: 1500 Gems, Pizza Girl Unit (requires level 100+)</li>
<li><strong>superluffysubcount</strong>: 1500 Gems, Zorro Mystical Unit (requires level 100+)</li>
<li><strong>sbrupdate</strong>: 240 Stardust, 9000 Gems (requires level 40+)</li>
<li><strong>omgupdate2024</strong>: 240 Stardust, 9000 Gems (requires level 80+)</li>
</ul>
<h3>Expired ASTD Codes</h3>
<ul>
<li>animecardbattle</li>
<li>egoupdate</li>
<li>fraudkunaupdate</li>
<li>miniupd393</li>
<li>tradingupdate</li>
<li>thankyoufor6bvisits</li>
<li>HunterStarPass</li>
<li>hope2024</li>
<li>EnumaElish2024</li>
<li>animeroulette1</li>
<li>thankyoufor6bvisits</li>
<li>24hoursupdate</li>
<li>tournamentstart</li>
<li>blamspot500kcodeunitrelease</li>
<li>UpdateThisWeek</li>
<li>videocode12135</li>
<li>newupdate1121</li>
<li>happyholidays1</li>
<li>sorry4delay</li>
<li>happyholidays2</li>
<li>PreChriistmas2023</li>
<li>newstarpass69</li>
<li>happy3yearanniversary</li>
<li>srry4delay</li>
<li>happyspookymonth</li>
<li>newupdatecode</li>
</ul>
<p>If you love tower defense games, then you must try out Skibidi Tower Defense, Anime Defenders, and Anime Vanguards as well. Furthermore, you should also explore our Roblox game codes to discover even more similar Roblox titles.</p>
<h2>How to Redeem All Star Tower Defense Codes</h2>
<p>To learn how to redeem codes for All Star Tower Defense, follow the step-by-step process below:</p>
<ul>
<li>Launch <a href="https://www.roblox.com/games/4996049426/UPDATE-4X-All-Star-Tower-Defense#about" rel="noopener nofollow" target="_blank">All Star Tower Defense</a> in the Roblox player.</li>
<li>Click on the <strong>Settings</strong> icon at the top left of your screen.</li>
<li>Input an ASTD code in the “<strong>Enter code here</strong>” text box and it will auto apply.</li>
</ul>
<figure><img decoding="async" alt="ASTD Codes" src="https://techaiconnect.com/wp-content/uploads/2025/06/ASTD-Codes.jpg"/></figure>
<p>To use ASTD codes, you need to level up in this game. All the codes listed above require a certain experience level and gameplay time to work. Make sure you have <strong>reached the required level</strong> to redeem the codes.</p>
<h2>How to Get More All Star Tower Defense Codes</h2>
<p>For all our readers’ convenience, we keep our code lists up-to-date. So, if you bookmark this page, you will never miss out on a new code. However, if you are willing to put some time into searching for codes by yourself, there are some official handles of the game that you must check out. You can check out the ASTD community discord and <a href="https://x.com/AllStarTowerDef" rel="noopener nofollow" target="_blank">All Star Tower Defense X account</a> for new codes.</p>
<p>If a code does not work, let us know in the comments below, and we will remove it from our ASTD codes list above. The developers share promo codes on every milestone like a specific likes goal or any new update of the game. Furthermore, share codes we might have missed out in the comments section.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/all-star-tower-defense-codes-free-gems-and-stardust/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>All Active Roblox Fisch Codes for June 2025</title>
		<link>https://techaiconnect.com/all-active-roblox-fisch-codes-for-june-2025/</link>
					<comments>https://techaiconnect.com/all-active-roblox-fisch-codes-for-june-2025/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sun, 13 Jul 2025 20:38:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[Fisch codes]]></category>
		<category><![CDATA[fishing simulator]]></category>
		<category><![CDATA[freebies]]></category>
		<category><![CDATA[Gaming]]></category>
		<category><![CDATA[Roblox]]></category>
		<category><![CDATA[Roblox Fisch codes]]></category>
		<category><![CDATA[video games]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4236</guid>

					<description><![CDATA[Check out the latest active Fisch codes for Roblox as of June 16, 2025. Redeem these codes for free cash, faster lure speed, baits, and other useful items.]]></description>
										<content:encoded><![CDATA[<p>Update: checked for new Roblox Fisch codes on June 16, 2025</p>
<p>While I enjoy fishing in real life, few video games capture that experience like Roblox Fisch. In this immersive fishing simulator, you can upgrade your gear at the merchant store to catch rarer fish. We&#8217;ve gathered all active Roblox Fisch codes that offer free cash, faster lure speed, baits, and other valuable in-game items. Use these freebies to enhance your rods and increase your chances of catching top-tier fish.</p>
<h2>All New Fisch Codes</h2>
<ul>
<li><strong>Legomy</strong>: Gullible Title</li>
<li><strong>SorryReward</strong>: 5 Kraken Tentacle baits and 1500 Cash</li>
<li><strong>THEKRAKEN</strong>: Sunken Ship bobber and 2500 Cash</li>
<li><strong>CARBON</strong>: Free Carbon bobber </li>
<li><strong>SORRYGUYS</strong>: 2 Kraken Tentacle baits and 1000 Cash </li>
<li><strong>ATLANTEANSTORM</strong>: 1000 Cash and 2 Hangman’s Hook baits </li>
<li><strong>GOLDENTIDE</strong>: 2000 Cash and 2 Instant Catchers </li>
<li><strong>NewYear</strong>: 1000 Cash, 2 Holy Berry, and 2 Peppermint Worm</li>
<li><strong>NorthernExpedition</strong>: 1000 Cash, 2 Holy Berry, and 3 Peppermint Worm</li>
</ul>
<p>While you are here, check out how to get to the second sea in Fisch. Once there, explore the full Fisch second sea map. Also, learn about all the second sea rods here.</p>
<h3>Expired Fisch Codes</h3>
<ul>
<li>FISCHMASDAY</li>
<li>MERRYFISCHMAS</li>
<li>RFG</li>
<li>NewBeginnings</li>
<li>1BVisits</li>
<li>GOODBYEFISCHMAS</li>
<li>ThankYouFollowers3</li>
<li>ThankYouFollowers2</li>
<li>Advent</li>
<li>ThankYouFollowers</li>
<li>AncientIsle</li>
<li>Prehistoric</li>
<li>TheDepths</li>
<li>100M</li>
<li>200K</li>
<li>SorryForDowntime</li>
<li>Scubaaaa</li>
<li>ThanksFor10Mil</li>
<li>FischFright2024</li>
<li>SorryforShutdown</li>
</ul>
<p>If you are tired of fishing all day, check out Type Soul codes and Blox Fruits codes for some anime fun. We also have a Roblox game codes master list with all the popular games for your convenience.</p>
<h2>How to Redeem Fisch Codes</h2>
<p>Catching the rarest fish can be challenging, but redeeming the latest Roblox Fisch codes is straightforward. Here&#8217;s how to do it:</p>
<ul>
<li>Launch the <a href="https://www.roblox.com/games/16732694052/Fisch" rel="noopener nofollow" target="_blank">Fisch</a> experience in the Roblox app.</li>
<li>Click the <strong>Menu button</strong> at the top of the screen.</li>
<li>Scroll down the menu to find the <strong>[Codes]</strong> section.</li>
<li>Input an active code in the Type Code Here area and press Enter.</li>
</ul>
<figure><img decoding="async" alt="Redeem Fisch codes option" src="https://techaiconnect.com/wp-content/uploads/2025/06/Redeem-Fisch-codes-option.jpg"/></figure>
<h2>How to Get More Fisch Codes</h2>
<p>To get the latest codes, staying connected with us is your best bet. Bookmark this page to never miss out on new Roblox Fisch codes. We constantly search for working codes while removing expired ones. Alternatively, you can follow the developer’s official socials to see if they’ve shared any new codes.</p>
<p>The ideal place to check first is the official <a href="https://discord.com/invite/cuKz5SK3md" rel="noopener nofollow" target="_blank">Fisch Discord server</a>. In this server, you can find new codes in three different channels: announcements, sub-announcements, and updates. You can also find more information about the latest updates on the @WoozyNate X account and Fisching Roblox group.</p>
<h2>Other Ways to Get Rewards in Fisch</h2>
<p>Besides these codes, you can earn additional free rewards in the game. When you join Fisch for the first time, you receive <strong>200 Cash</strong> as a joining bonus. You can also complete quests to earn more cash.</p>
<p>Fisch developers occasionally host events where you <strong>need to complete certain tasks to earn exciting in-game resources</strong>. These rewards can unlock many rods and enhance your potential to catch rare fish in the game.</p>
<p>With these working Roblox Fisch codes, you can now become the best fisherman. Have you started your fishing journey yet? Tell us in the comments below.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/all-active-roblox-fisch-codes-for-june-2025/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>All New Attack on Titan Revolution Codes for June 2025</title>
		<link>https://techaiconnect.com/all-new-attack-on-titan-revolution-codes-for-june-2025/</link>
					<comments>https://techaiconnect.com/all-new-attack-on-titan-revolution-codes-for-june-2025/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Wed, 09 Jul 2025 14:42:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[AOT Revolution codes]]></category>
		<category><![CDATA[Attack on Titan]]></category>
		<category><![CDATA[Attack on Titan Revolution codes]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[free spins]]></category>
		<category><![CDATA[game codes]]></category>
		<category><![CDATA[Gaming]]></category>
		<category><![CDATA[Roblox]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4208</guid>

					<description><![CDATA[Discover the newest Attack on Titan Revolution codes updated on June 17, 2025. Redeem these codes for free spins, luck boosts, and more to enhance your gameplay.]]></description>
										<content:encoded><![CDATA[<p>Update: checked for new Attack on Titan Revolution codes on June 17, 2025</p>
<p>Attack on Titan Revolution is a Roblox experience inspired by the iconic Attack on Titan animanga series. In this game, you step into Eren’s shoes, soar through Shiganshina with your ODM gear, and take down Titans. To advance, unlock superior weapons, and much more, you&#8217;ll need all the Attack on Titan Revolution codes for spins and luck boosts.</p>
<h2>All New Attack on Titan Revolution Codes</h2>
<h3>Working AOT Revolution Codes</h3>
<ul>
<li><strong>LIKES750K</strong>: 250 Spins, 5 Emperor Keys, and 2x Luck Potion [1H] (<mark class="has-inline-color has-gradient-red-color"><strong>NEW</strong></mark>)</li>
</ul>
<h3>Expired AOT Revolution Codes</h3>
<ul>
<li>UPDATE4PENDING</li>
<li>LIKES700K</li>
<li>MEMBERS750K</li>
<li>LIKES675K</li>
<li>LIKES650K</li>
<li>LIKES625K</li>
<li>LIKES600K</li>
<li>UPDATE3</li>
<li>TRADING</li>
<li>LIKES575K</li>
<li>LIKES550K</li>
<li>MEMBERS600K</li>
<li>SUB2EK2</li>
<li>LIKES525K</li>
<li>LIKES500K</li>
<li>LIKES475K</li>
<li>UPDATE3SOON</li>
<li>LIKES450K</li>
<li>FREESPIN2</li>
<li>SUB2EK</li>
<li>BIGPATCH</li>
<li>LIKES425K</li>
<li>UPDATE2POINT5</li>
<li>NEWYEARS2024</li>
<li>HOLIDAYS2024</li>
<li>LIKES400K</li>
<li>500kCommunityMembers</li>
<li>Thanksgiving</li>
<li>CODESEXPIREAFTER1WEEK</li>
<li>VISITS200M</li>
<li>SPOOKYUPDATESOON</li>
<li>UPDATE2SPINS</li>
<li>UPDATE2DEMON</li>
<li>UPDATE2FATE</li>
<li>UPDATE2HALLOWEEN</li>
<li>COLESPINS</li>
<li>ENDERSPINS</li>
<li>UPDATE2PATCH</li>
<li>LIKES350K</li>
<li>ARMOREDTITANSOON</li>
<li>SOSORRY4DELAY</li>
<li>ROBLOXFIX</li>
<li>LIKES325K</li>
<li>DEVCODE3</li>
<li>MYBAD</li>
<li>UPDATESOON2</li>
<li>SORRY4DELAY3</li>
<li>LIKES300K</li>
<li>RANDOMCODE1</li>
<li>RANDOMCODE2</li>
<li>LIKES280K</li>
<li>VISITS100M</li>
<li>UPDATE2SOON</li>
<li>LUCKBOOST</li>
<li>LIKES260K</li>
<li>MEMBERS450K</li>
<li>REVERT1</li>
<li>COMPENSATE1</li>
<li>COMPENSATE2</li>
<li>APOLOGIES2</li>
<li>LIKES240K</li>
<li>PLAYERS60K</li>
<li>APOLOGIES</li>
<li>MEMBERS300K</li>
<li>MEMBERS350K</li>
<li>MEMBERS400K</li>
<li>DEVCODE2</li>
<li>LIKES220K</li>
<li>UPDATE1</li>
<li>LIKES145K</li>
<li>SORRY4DELAY</li>
<li>SORRY4DELAY2</li>
<li>LIKES200K</li>
<li>LIKES175K</li>
<li>LIKES160K</li>
<li>MEMBERS250K</li>
<li>LIKES130K</li>
<li>LIKES115k</li>
<li>LIKES100K</li>
<li>MEMBERS200K</li>
<li>LIKES90K</li>
<li>UPDATE1SOON</li>
<li>MEMBERS175K</li>
<li>MEMBERS150K</li>
<li>LIKES80K</li>
<li>LIKES70K</li>
<li>LIKES60K</li>
<li>MEMBERS125K</li>
<li>LIKES15K</li>
<li>MEMBERS30K</li>
</ul>
<p>While you are here, check out codes for other Attack on Titan-inspired games like Attack on Titan Evolution or Untitled Attack on Titan and start slaying Titans to save your people.</p>
<h2>How to Redeem Attack on Titan Revolution Codes</h2>
<p>You must be <strong>at least level 15</strong> to redeem these working AOT Revolution codes. Be quick to grab all the spins as the codes expire fast. To get free spins, follow the step-by-step process below:</p>
<ul>
<li>Launch <a href="https://www.roblox.com/games/13379208636/RELEASE-Attack-on-Titan-Revolution" rel="noopener nofollow" target="_blank">Attack on Titan Revolution</a> in the Roblox app.</li>
<li>Select the <strong>Codes option</strong> from the main menu.</li>
<li>Type a code in the Enter Code area at the bottom right.</li>
<li>Click the <strong>Redeem</strong> button to get your free reward.</li>
</ul>
<figure><img decoding="async" alt="AOTR Redeem codes section" src="https://techaiconnect.com/wp-content/uploads/2025/06/AOTR-Redeem-codes-section.jpg"/></figure>
<h2>How to Get More Attack on Titan Revolution Codes</h2>
<p>Bookmarking our page is the best way to stay up-to-date with the latest working codes. However, if you want to do the tedious job of finding the codes yourself, check the official <a href="https://www.roblox.com/groups/17347863/AoTR-PI#!/about" rel="noopener nofollow" target="_blank">AOTR Roblox group</a>, the developer <a href="https://x.com/_EvolutionPower" rel="noopener nofollow" target="_blank">@_EvolutionPower</a> on X, and the <a href="https://discord.com/invite/aotrevolution" rel="noopener nofollow" target="_blank">AOT Revolution Discord server</a>. You can also look for new codes on the AOTR Roblox page linked above.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/all-new-attack-on-titan-revolution-codes-for-june-2025/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Othello online 2 player</title>
		<link>https://techaiconnect.com/othello-online-2-player/</link>
					<comments>https://techaiconnect.com/othello-online-2-player/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 08:21:38 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4291</guid>

					<description><![CDATA[Othello Online (Reversi) Othello (Reversi) Flip your opponent's discs to win! Your Name Create New [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Othello Online (Reversi)</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts: Inter -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #f0f0f0;
        }
        .board {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            grid-template-rows: repeat(8, 1fr);
            width: 100%;
            max-width: 640px;
            aspect-ratio: 1 / 1;
            background-color: #047857; /* emerald-700 */
            border: 8px solid #4a3a2a;
            border-radius: 8px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.4);
            padding: 5px;
            gap: 5px;
        }
        .square {
            display: flex;
            align-items: center;
            justify-content: center;
            background-color: #059669; /* emerald-600 */
            border-radius: 4px;
            cursor: pointer;
            transition: background-color 0.2s;
        }
        .square:hover {
            background-color: #047857;
        }

        .piece {
            width: 85%;
            height: 85%;
            border-radius: 50%;
            box-shadow: inset 0 2px 4px rgba(0,0,0,0.4), 0 2px 3px rgba(0,0,0,0.3);
            transform: scale(0);
            animation: place-piece 0.3s ease-out forwards;
        }
        .piece.black { background-color: #1f2937; } /* gray-800 */
        .piece.white { background-color: #f9fafb; } /* gray-50 */

        @keyframes place-piece {
            from { transform: scale(0); }
            to { transform: scale(1); }
        }

        .valid-move-indicator {
            width: 40%;
            height: 40%;
            border-radius: 50%;
            background-color: rgba(255, 255, 255, 0.3);
            pointer-events: none;
        }
        
        .board-disabled {
            pointer-events: none;
            opacity: 0.8;
        }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-bold text-white">Othello (Reversi)</h1>
            <p class="text-gray-400 mt-2">Flip your opponent's discs to win!</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your name" required>
            </div>
            <button id="createGameBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinGameId" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter Room ID (6 digits)">
                <button id="joinGameBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <div id="game-container" class="hidden w-full">
            <div class="flex flex-col lg:flex-row gap-6 items-start justify-center">
                <!-- Game Info Panel -->
                <div class="w-full lg:w-80 bg-gray-800 p-5 rounded-xl shadow-lg border border-gray-700 flex-shrink-0 order-last lg:order-first">
                    <h3 class="text-xl font-semibold mb-3 text-white">Game Information</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="gameIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest"/>
                            <button id="copyGameIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>

                    <div id="playerInfo" class="space-y-3 text-sm"></div>
                    <p id="game-status" class="mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-200"></p>
                    
                    <button id="resetGameBtn" class="w-full mt-4 text-white bg-yellow-600 hover:bg-yellow-700 focus:ring-4 focus:outline-none focus:ring-yellow-800 font-medium rounded-lg text-sm px-5 py-3 text-center hidden">Play Again</button>
                    <button id="leaveGameBtn" class="w-full mt-2 text-white bg-gray-600 hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-gray-800 font-medium rounded-lg text-sm px-5 py-3 text-center">Leave Room</button>
                </div>
                
                <!-- Game Board -->
                <div id="board" class="board">
                    <!-- Squares will be generated by JS -->
                </div>
            </div>
        </div>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        // Import functions from Firebase SDKs
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                // Fallback configuration
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id';
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const boardEl = document.getElementById('board');
        const gameStatusEl = document.getElementById('game-status');
        const playerInfoEl = document.getElementById('playerInfo');
        const createGameBtn = document.getElementById('createGameBtn');
        const joinGameBtn = document.getElementById('joinGameBtn');
        const resetGameBtn = document.getElementById('resetGameBtn');
        const leaveGameBtn = document.getElementById('leaveGameBtn');
        const copyGameIdBtn = document.getElementById('copyGameIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const gameIdDisplay = document.getElementById('gameIdDisplay');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE & CONSTANTS ---
        const BOARD_SIZE = 8;
        let userId = null;
        let playerName = '';
        let currentGameId = null;
        let playerSymbol = null; // 'B' for Black, 'W' for White
        let unsubscribeGame = null;
        let isHost = false;

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showGameView(gameId) {
            currentGameId = gameId;
            gameIdDisplay.value = gameId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeGame) {
                unsubscribeGame();
                unsubscribeGame = null;
            }
            currentGameId = null;
            playerSymbol = null;
            isHost = false;
        }

        // --- OTHELLO GAME LOGIC ---

        /**
         * Checks for valid moves for a given player.
         * @param {Array<Array<string|null>>} board - The 8x8 game board.
         * @param {string} player - The player to check for ('B' or 'W').
         * @returns {Array<{row: number, col: number}>} An array of valid move coordinates.
         */
        function getValidMoves(board, player) {
            const validMoves = [];
            const opponent = player === 'B' ? 'W' : 'B';
            const directions = [
                [-1, -1], [-1, 0], [-1, 1],
                [0, -1],           [0, 1],
                [1, -1], [1, 0], [1, 1]
            ];

            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    if (board[r][c] !== null) continue; // Must be an empty square

                    let isValid = false;
                    for (const [dr, dc] of directions) {
                        let row = r + dr;
                        let col = c + dc;
                        let hasOpponentPieces = false;

                        while (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === opponent) {
                            hasOpponentPieces = true;
                            row += dr;
                            col += dc;
                        }

                        if (hasOpponentPieces && row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === player) {
                            isValid = true;
                            break; // Found a valid direction, no need to check others for this square
                        }
                    }
                    if (isValid) {
                        validMoves.push({ row: r, col: c });
                    }
                }
            }
            return validMoves;
        }

        /**
         * Gets all pieces that would be flipped by a move.
         * @param {Array<Array<string|null>>} board - The game board.
         * @param {number} startRow - The row of the new piece.
         * @param {number} startCol - The column of the new piece.
         * @param {string} player - The current player ('B' or 'W').
         * @returns {Array<{row: number, col: number}>} An array of coordinates of pieces to flip.
         */
        function getFlipsForMove(board, startRow, startCol, player) {
            const opponent = player === 'B' ? 'W' : 'B';
            const directions = [
                [-1, -1], [-1, 0], [-1, 1],
                [0, -1],           [0, 1],
                [1, -1], [1, 0], [1, 1]
            ];
            const piecesToFlip = [];

            for (const [dr, dc] of directions) {
                let row = startRow + dr;
                let col = startCol + dc;
                const potentialFlips = [];

                while (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === opponent) {
                    potentialFlips.push({ row, col });
                    row += dr;
                    col += dc;
                }

                if (row >= 0 && row < BOARD_SIZE && col >= 0 && col < BOARD_SIZE && board[row][col] === player) {
                    piecesToFlip.push(...potentialFlips);
                }
            }
            return piecesToFlip;
        }

        // --- FIREBASE & GAME FLOW ---

        function getGameDocRef(gameId) {
            return doc(db, `artifacts/${appId}/public/data/othello-games/${gameId}`);
        }

        function createInitialBoard() {
            const board = Array(BOARD_SIZE).fill(null).map(() => Array(BOARD_SIZE).fill(null));
            board[3][3] = 'W';
            board[3][4] = 'B';
            board[4][3] = 'B';
            board[4][4] = 'W';
            return board;
        }

        function calculateScores(board) {
            let blackScore = 0;
            let whiteScore = 0;
            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    if (board[r][c] === 'B') blackScore++;
                    if (board[r][c] === 'W') whiteScore++;
                }
            }
            return { B: blackScore, W: whiteScore };
        }

        async function generateUniqueGameId() {
            let gameId;
            let attempts = 0;
            const maxAttempts = 20;
            while (attempts < maxAttempts) {
                gameId = Math.floor(100000 + Math.random() * 900000).toString();
                const docSnap = await getDoc(getGameDocRef(gameId));
                if (!docSnap.exists()) return gameId;
                attempts++;
            }
            throw new Error("Failed to generate a unique room ID.");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!userId) {
                showMessage("Error", "User ID not available yet. Please wait a moment.");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = 'Creating...';

            try {
                const newGameId = await generateUniqueGameId();
                const gameDocRef = getGameDocRef(newGameId);
                const initialBoard = createInitialBoard();
                const newGame = {
                    board: JSON.stringify(initialBoard),
                    players: { p1: { id: userId, name: playerName } }, // p1 is Black ('B')
                    currentPlayer: 'B',
                    status: 'waiting',
                    hostId: userId,
                    scores: { B: 2, W: 2 },
                    winner: null,
                    createdAt: new Date().toISOString(),
                };
                await setDoc(gameDocRef, newGame);
                
                isHost = true;
                playerSymbol = 'B';
                listenToGameUpdates(newGameId);
                showGameView(newGameId);

            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                
                if (!gameData.players.p2 && gameData.players.p1.id !== userId) {
                    await updateDoc(gameDocRef, {
                        'players.p2': { id: userId, name: playerName },
                        status: 'active'
                    });
                    playerSymbol = 'W';
                } else {
                    playerSymbol = gameData.players.p1.id === userId ? 'B' : 'W';
                }
                
                isHost = gameData.hostId === userId;
                listenToGameUpdates(gameIdToJoin);
                showGameView(gameIdToJoin);

            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join room. Please check the ID.");
            } finally {
                joinGameBtn.disabled = false;
            }
        }
        
        async function onSquareClick(row, col, isValidMove) {
            if (!isValidMove) return;

            const gameDocRef = getGameDocRef(currentGameId);
            const gameSnap = await getDoc(gameDocRef);
            if (!gameSnap.exists()) return;

            const gameData = gameSnap.data();
            const board = JSON.parse(gameData.board);
            const currentPlayer = gameData.currentPlayer;

            if (currentPlayer !== playerSymbol) return;

            // Apply the move
            const flips = getFlipsForMove(board, row, col, currentPlayer);
            board[row][col] = currentPlayer;
            flips.forEach(p => { board[p.row][p.col] = currentPlayer; });

            // Determine next state
            let nextPlayer = currentPlayer === 'B' ? 'W' : 'B';
            let nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            
            // If next player has no moves, it's current player's turn again
            if (nextPlayerValidMoves.length === 0) {
                nextPlayer = currentPlayer;
                nextPlayerValidMoves = getValidMoves(board, nextPlayer);
            }
            
            const newScores = calculateScores(board);
            let newStatus = 'active';
            let winner = null;

            // Check for game over
            if (nextPlayerValidMoves.length === 0) {
                newStatus = 'finished';
                if (newScores.B > newScores.W) winner = 'B';
                else if (newScores.W > newScores.B) winner = 'W';
                else winner = 'draw';
            }

            await updateDoc(gameDocRef, {
                board: JSON.stringify(board),
                currentPlayer: nextPlayer,
                scores: newScores,
                status: newStatus,
                winner: winner
            });
        }

        async function resetCurrentGame() {
            if (!isHost || !currentGameId) return;
            const gameDocRef = getGameDocRef(currentGameId);
            const initialBoard = createInitialBoard();
            await updateDoc(gameDocRef, {
                board: JSON.stringify(initialBoard),
                currentPlayer: 'B',
                status: 'active',
                scores: { B: 2, W: 2 },
                winner: null
            });
        }

        // --- RENDERING ---

        function renderBoard(gameData) {
            const { board: boardStr, currentPlayer, status } = gameData;
            const board = JSON.parse(boardStr);
            const isMyTurn = currentPlayer === playerSymbol && status === 'active';
            
            boardEl.innerHTML = '';
            boardEl.classList.toggle('board-disabled', !isMyTurn);
            
            const validMoves = isMyTurn ? getValidMoves(board, playerSymbol) : [];

            for (let r = 0; r < BOARD_SIZE; r++) {
                for (let c = 0; c < BOARD_SIZE; c++) {
                    const squareEl = document.createElement('div');
                    squareEl.className = 'square';
                    
                    const piece = board[r][c];
                    if (piece) {
                        const pieceEl = document.createElement('div');
                        pieceEl.className = `piece ${piece === 'B' ? 'black' : 'white'}`;
                        squareEl.appendChild(pieceEl);
                    }

                    const isValidMove = validMoves.some(move => move.row === r && move.col === c);
                    if (isValidMove) {
                        const indicator = document.createElement('div');
                        indicator.className = 'valid-move-indicator';
                        squareEl.appendChild(indicator);
                    }

                    squareEl.addEventListener('click', () => onSquareClick(r, c, isValidMove));
                    boardEl.appendChild(squareEl);
                }
            }
        }

        function updateGameInfo(gameData) {
            const { players, status, currentPlayer, scores, winner } = gameData;
            const p1 = players.p1; // Black
            const p2 = players.p2; // White

            playerInfoEl.innerHTML = `
                <div class="flex items-center justify-between p-3 rounded-lg transition-colors ${currentPlayer === 'B' && status === 'active' ? 'bg-gray-600' : 'bg-gray-700'}">
                    <div class="flex items-center gap-3">
                        <div class="w-6 h-6 rounded-full bg-gray-800 border-2 border-gray-500"></div>
                        <span class="font-semibold text-white">${p1.name} ${p1.id === userId ? "(You)" : ""}</span>
                    </div>
                    <span class="font-bold text-xl">${scores.B}</span>
                </div>
            `;

            if (p2) {
                 playerInfoEl.innerHTML += `
                    <div class="flex items-center justify-between p-3 rounded-lg transition-colors ${currentPlayer === 'W' && status === 'active' ? 'bg-gray-600' : 'bg-gray-700'}">
                        <div class="flex items-center gap-3">
                            <div class="w-6 h-6 rounded-full bg-gray-50 border-2 border-gray-400"></div>
                            <span class="font-semibold text-white">${p2.name} ${p2.id === userId ? "(You)" : ""}</span>
                        </div>
                        <span class="font-bold text-xl">${scores.W}</span>
                    </div>
                 `;
            } else {
                 playerInfoEl.innerHTML += `<div class="p-3 text-center text-gray-400 border border-dashed rounded-lg border-gray-600">Waiting for opponent...</div>`;
            }
            
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            if (status === 'waiting') {
                statusText = 'Waiting for player...';
            } else if (status === 'active') {
                const currentName = currentPlayer === 'B' ? p1.name : (p2 ? p2.name : '');
                statusText = `${currentName}'s Turn`;
                if (currentPlayer === playerSymbol) {
                    statusText = "Your Turn!";
                    statusClass = 'bg-blue-600 text-white';
                }
            } else if (status === 'finished') {
                if (winner === 'draw') {
                    statusText = "It's a Draw!";
                } else {
                    const winnerName = winner === 'B' ? p1.name : p2.name;
                    statusText = `${winnerName} Wins!`;
                }
                statusClass = 'bg-yellow-500 text-black font-bold';
            }
            gameStatusEl.textContent = statusText;
            gameStatusEl.className = `mt-4 text-center font-semibold text-lg p-3 rounded-lg ${statusClass}`;
            
            resetGameBtn.classList.toggle('hidden', status !== 'finished' || !isHost);
        }

        function listenToGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = getGameDocRef(gameId);
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    renderBoard(gameData);
                    updateGameInfo(gameData);
                } else {
                    showMessage("Notice", "The game room has been closed or does not exist.");
                    showLobbyView();
                }
            });
        }

        // --- EVENT LISTENERS ---
        createGameBtn.addEventListener('click', createNewGame);
        joinGameBtn.addEventListener('click', joinExistingGame);
        leaveGameBtn.addEventListener('click', showLobbyView);
        resetGameBtn.addEventListener('click', resetCurrentGame);
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));
        
        copyGameIdBtn.addEventListener('click', () => {
            const gameId = gameIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/othello-online-2-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Chess Online Free (2 player)</title>
		<link>https://techaiconnect.com/chess-online-2-player/</link>
					<comments>https://techaiconnect.com/chess-online-2-player/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sun, 06 Jul 2025 07:50:57 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4285</guid>

					<description><![CDATA[Online Chess (Firestore) Online Chess Challenge your friends to a game of wits Your Name [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Online Chess (Firestore)</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Chess.js for game logic -->
    <script src="https://cdnjs.cloudflare.com/ajax/libs/chess.js/0.10.3/chess.min.js"></script>
    
    <!-- Google Fonts: Inter -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Inter', sans-serif;
            background-color: #1a1a1a;
            color: #f0f0f0;
        }
        .board {
            display: grid;
            grid-template-columns: repeat(8, 1fr);
            grid-template-rows: repeat(8, 1fr);
            width: 100%;
            max-width: 640px;
            aspect-ratio: 1 / 1;
            border: 4px solid #4a3a2a;
            border-radius: 8px;
            box-shadow: 0 10px 20px rgba(0,0,0,0.4);
        }
        .square {
            display: flex;
            align-items: center;
            justify-content: center;
            font-size: clamp(20px, 6vmin, 48px);
            user-select: none;
        }
        .square.light { background-color: #f0d9b5; }
        .square.dark { background-color: #b58863; }
        
        .piece { cursor: grab; }
        .piece:active { cursor: grabbing; }

        /* Styling for highlights */
        .selected {
            background-color: rgba(34, 197, 94, 0.7) !important; /* green-500 with opacity */
        }
        .last-move {
            background-color: rgba(245, 158, 11, 0.5) !important; /* amber-500 with opacity */
        }
        .possible-move-dot {
            width: 30%;
            height: 30%;
            background-color: rgba(40, 40, 40, 0.4);
            border-radius: 50%;
            pointer-events: none; /* Make sure it doesn't block clicks on the square */
        }
        .in-check {
             background-color: rgba(239, 68, 68, 0.6) !important; /* red-500 with opacity */
        }
        
        .board-disabled {
            pointer-events: none;
            opacity: 0.8;
        }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-bold text-white">Online Chess</h1>
            <p class="text-gray-400 mt-2">Challenge your friends to a game of wits</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter your name" required>
            </div>
            <button id="createGameBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinGameId" class="w-full text-sm rounded-lg p-2.5 bg-gray-700 border-gray-600 placeholder-gray-400 text-white focus:ring-blue-500 focus:border-blue-500" placeholder="Enter Room ID (6 digits)">
                <button id="joinGameBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <div id="game-container" class="hidden w-full">
            <div class="flex flex-col lg:flex-row gap-6 items-start justify-center">
                <!-- Game Info Panel -->
                <div class="w-full lg:w-80 bg-gray-800 p-5 rounded-xl shadow-lg border border-gray-700 flex-shrink-0 order-last lg:order-first">
                    <h3 class="text-xl font-semibold mb-3 text-white">Game Information</h3>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="gameIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest"/>
                            <button id="copyGameIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>

                    <div id="playerInfo" class="space-y-3 text-sm"></div>
                    <p id="game-status" class="mt-4 text-center font-semibold text-lg p-3 rounded-lg bg-gray-700 text-gray-200"></p>
                    
                    <div id="captured-pieces-white" class="mt-4 text-2xl"></div>
                    <div id="captured-pieces-black" class="mt-4 text-2xl"></div>

                    <button id="resetGameBtn" class="w-full mt-4 text-white bg-yellow-600 hover:bg-yellow-700 focus:ring-4 focus:outline-none focus:ring-yellow-800 font-medium rounded-lg text-sm px-5 py-3 text-center hidden">Play Again</button>
                    <button id="leaveGameBtn" class="w-full mt-2 text-white bg-gray-600 hover:bg-gray-500 focus:ring-4 focus:outline-none focus:ring-gray-800 font-medium rounded-lg text-sm px-5 py-3 text-center">Leave Room</button>
                </div>
                
                <!-- Game Board -->
                <div id="board" class="board">
                    <!-- Squares will be generated by JS -->
                </div>
            </div>
        </div>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId;
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const boardEl = document.getElementById('board');
        const gameStatusEl = document.getElementById('game-status');
        const playerInfoEl = document.getElementById('playerInfo');
        const createGameBtn = document.getElementById('createGameBtn');
        const joinGameBtn = document.getElementById('joinGameBtn');
        const resetGameBtn = document.getElementById('resetGameBtn');
        const leaveGameBtn = document.getElementById('leaveGameBtn');
        const copyGameIdBtn = document.getElementById('copyGameIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const gameIdDisplay = document.getElementById('gameIdDisplay');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let userId = null;
        let playerName = '';
        let currentGameId = null;
        let playerColor = null; // 'w' or 'b'
        let unsubscribeGame = null;
        let chess = new Chess();
        let selectedSquare = null;
        let isHost = false;

        // Unicode pieces based on FEN standard (Uppercase: White, Lowercase: Black)
        const pieceUnicode = {
            'P': '♙', 'R': '♖', 'N': '♘', 'B': '♗', 'Q': '♕', 'K': '♔',
            'p': '♟', 'r': '♜', 'n': '♞', 'b': '♝', 'q': '♛', 'k': '♚'
        };

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showGameView(gameId) {
            currentGameId = gameId;
            gameIdDisplay.value = gameId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeGame) {
                unsubscribeGame();
                unsubscribeGame = null;
            }
            currentGameId = null;
            playerColor = null;
            isHost = false;
        }

        // --- GAME LOGIC ---
        function getGameDocRef(gameId) {
            return doc(db, `artifacts/${appId}/public/data/chess-games/${gameId}`);
        }

        function renderBoard(fen, lastMove, playerColorToMove) {
            chess.load(fen);
            boardEl.innerHTML = '';
            boardEl.classList.toggle('board-disabled', playerColor !== playerColorToMove || chess.game_over());

            const squares = chess.SQUARES;
            const isFlipped = playerColor === 'b';

            for (let i = 0; i < squares.length; i++) {
                // If board is flipped for black player, iterate from the end of the squares array
                const squareIndex = isFlipped ? (squares.length - 1 - i) : i;
                const squareName = squares[squareIndex];

                const squareEl = document.createElement('div');
                
                // Determine square color from its actual rank and file
                const rank = parseInt(squareName.charAt(1), 10);
                const file = squareName.charCodeAt(0) - 'a'.charCodeAt(0);
                // A1 (rank 1, file 0) is dark. (1+0)%2 != 0. Correct.
                // H1 (rank 1, file 7) is light. (1+7)%2 == 0. Correct.
                squareEl.className = `square ${(rank + file) % 2 === 0 ? 'light' : 'dark'}`;
                squareEl.dataset.square = squareName;

                const piece = chess.get(squareName);
                if (piece) {
                    const pieceEl = document.createElement('span');
                    pieceEl.className = 'piece';
                    
                    // Get the FEN character for the piece to use with the unicode map
                    let fenChar = piece.type;
                    if (piece.color === 'w') {
                        fenChar = fenChar.toUpperCase();
                    }
                    pieceEl.textContent = pieceUnicode[fenChar];
                    pieceEl.style.color = piece.color === 'w' ? '#FFFFFF' : '#000000';
                    squareEl.appendChild(pieceEl);
                }
                
                if (lastMove && (squareName === lastMove.from || squareName === lastMove.to)) {
                    squareEl.classList.add('last-move');
                }
                
                if (chess.in_check() && piece && piece.type === 'k' && piece.color === chess.turn()) {
                    squareEl.classList.add('in-check');
                }

                squareEl.addEventListener('click', () => onSquareClick(squareName));
                boardEl.appendChild(squareEl);
            }
        }
        
        function updateGameInfo(gameData) {
            const { players, status, turn } = gameData;
            const p1 = players.p1; // White
            const p2 = players.p2; // Black

            playerInfoEl.innerHTML = '';
            const p1_html = `<div class="flex items-center justify-between p-3 rounded-lg transition-colors ${turn === 'w' ? 'bg-green-800' : 'bg-gray-700'}">
                <span class="font-semibold text-white">White: ${p1.name}</span>
                <span class="font-mono text-xs text-gray-400">${p1.id === userId ? "(You)" : ""}</span>
            </div>`;
            playerInfoEl.insertAdjacentHTML('beforeend', p1_html);

            if (p2) {
                 const p2_html = `<div class="flex items-center justify-between p-3 rounded-lg transition-colors ${turn === 'b' ? 'bg-green-800' : 'bg-gray-700'}">
                    <span class="font-semibold text-white">Black: ${p2.name}</span>
                    <span class="font-mono text-xs text-gray-400">${p2.id === userId ? "(You)" : ""}</span>
                 </div>`;
                 playerInfoEl.insertAdjacentHTML('beforeend', p2_html);
            } else {
                 playerInfoEl.insertAdjacentHTML('beforeend', `<div class="p-3 text-center text-gray-400 border border-dashed rounded-lg border-gray-600">Waiting for opponent...</div>`);
            }
            
            // Update status message
            let statusText = '';
            let statusClass = 'bg-gray-700 text-gray-200';
            switch(status) {
                case 'waiting':
                    statusText = 'Waiting for player...';
                    break;
                case 'active':
                    statusText = turn === 'w' ? "White's Turn" : "Black's Turn";
                    if ((turn === 'w' && playerColor === 'w') || (turn === 'b' && playerColor === 'b')) {
                        statusText = "Your Turn!";
                        statusClass = 'bg-blue-600 text-white';
                    }
                    break;
                case 'checkmate':
                    const winner = turn === 'b' ? 'White' : 'Black';
                    statusText = `Checkmate! ${winner} wins!`;
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'stalemate':
                    statusText = 'Stalemate!';
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
                case 'draw':
                    statusText = 'Draw by repetition';
                    statusClass = 'bg-yellow-500 text-black font-bold';
                    break;
            }
            gameStatusEl.textContent = statusText;
            gameStatusEl.className = `mt-4 text-center font-semibold text-lg p-3 rounded-lg ${statusClass}`;
            
            resetGameBtn.classList.toggle('hidden', status === 'active' || status === 'waiting' || !isHost);
        }

        async function generateUniqueGameId() {
            let gameId;
            let attempts = 0;
            const maxAttempts = 20;
            while (attempts < maxAttempts) {
                gameId = Math.floor(100000 + Math.random() * 900000).toString();
                const docSnap = await getDoc(getGameDocRef(gameId));
                if (!docSnap.exists()) return gameId;
                attempts++;
            }
            throw new Error("Failed to generate a unique room ID.");
        }

        async function createNewGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!userId) {
                showMessage("Error", "User ID not available yet. Please wait a moment.");
                return;
            }

            createGameBtn.disabled = true;
            createGameBtn.textContent = 'Creating...';

            try {
                const newGameId = await generateUniqueGameId();
                const gameDocRef = getGameDocRef(newGameId);
                const newGame = {
                    fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
                    players: { p1: { id: userId, name: playerName } }, // p1 is White
                    turn: 'w',
                    status: 'waiting',
                    hostId: userId,
                    lastMove: null,
                    createdAt: new Date().toISOString(),
                };
                await setDoc(gameDocRef, newGame);
                
                isHost = true;
                playerColor = 'w';
                listenToGameUpdates(newGameId);
                showGameView(newGameId);

            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createGameBtn.disabled = false;
                createGameBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingGame() {
            playerName = document.getElementById('playerName').value.trim();
            if (!playerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const gameIdToJoin = document.getElementById('joinGameId').value.trim();
            if (!gameIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }

            joinGameBtn.disabled = true;
            const gameDocRef = getGameDocRef(gameIdToJoin);
            
            try {
                const gameSnap = await getDoc(gameDocRef);
                if (!gameSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const gameData = gameSnap.data();
                if (gameData.players.p2 && gameData.players.p1.id !== userId) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                
                if (!gameData.players.p2 && gameData.players.p1.id !== userId) {
                    await updateDoc(gameDocRef, {
                        'players.p2': { id: userId, name: playerName },
                        status: 'active'
                    });
                    playerColor = 'b';
                } else {
                    playerColor = gameData.players.p1.id === userId ? 'w' : 'b';
                }
                
                isHost = gameData.hostId === userId;
                listenToGameUpdates(gameIdToJoin);
                showGameView(gameIdToJoin);

            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join room. Please check the ID.");
            } finally {
                joinGameBtn.disabled = false;
            }
        }

        function listenToGameUpdates(gameId) {
            if (unsubscribeGame) unsubscribeGame();
            const gameDocRef = getGameDocRef(gameId);
            // FIX: Pass the document reference as the first argument to onSnapshot
            unsubscribeGame = onSnapshot(gameDocRef, (docSnap) => {
                if (docSnap.exists()) {
                    const gameData = docSnap.data();
                    renderBoard(gameData.fen, gameData.lastMove, gameData.turn);
                    updateGameInfo(gameData);
                } else {
                    showMessage("Notice", "The game room has been closed or does not exist.");
                    showLobbyView();
                }
            });
        }
        
        function clearHighlights() {
            document.querySelectorAll('.square').forEach(s => {
                s.classList.remove('selected');
                const dot = s.querySelector('.possible-move-dot');
                if (dot) dot.remove();
            });
        }

        async function onSquareClick(squareName) {
            const piece = chess.get(squareName);

            // If a piece is already selected, try to move it
            if (selectedSquare) {
                const move = {
                    from: selectedSquare,
                    to: squareName,
                    promotion: 'q' // Always promote to queen for simplicity
                };
                
                const result = chess.move(move);
                
                if (result) {
                    // Move is valid, update Firestore
                    let newStatus = 'active';
                    if (chess.in_checkmate()) newStatus = 'checkmate';
                    else if (chess.in_stalemate()) newStatus = 'stalemate';
                    else if (chess.in_draw()) newStatus = 'draw';

                    const gameDocRef = getGameDocRef(currentGameId);
                    await updateDoc(gameDocRef, {
                        fen: chess.fen(),
                        turn: chess.turn(),
                        status: newStatus,
                        lastMove: { from: result.from, to: result.to }
                    });
                }
                
                // Deselect square regardless of move validity
                clearHighlights();
                selectedSquare = null;

            } else {
                // No piece selected, so select one if it's the player's piece
                if (piece && piece.color === playerColor) {
                    selectedSquare = squareName;
                    clearHighlights();
                    document.querySelector(`[data-square="${squareName}"]`).classList.add('selected');
                    
                    // Show possible moves
                    const moves = chess.moves({ square: squareName, verbose: true });
                    moves.forEach(move => {
                        const moveSquare = document.querySelector(`[data-square="${move.to}"]`);
                        const dot = document.createElement('div');
                        dot.className = 'possible-move-dot';
                        moveSquare.appendChild(dot);
                    });
                }
            }
        }
        
        async function resetCurrentGame() {
            if (!isHost || !currentGameId) return;
            const gameDocRef = getGameDocRef(currentGameId);
            await updateDoc(gameDocRef, {
                fen: 'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1',
                turn: 'w',
                status: 'active',
                lastMove: null
            });
        }

        // --- EVENT LISTENERS ---
        createGameBtn.addEventListener('click', createNewGame);
        joinGameBtn.addEventListener('click', joinExistingGame);
        leaveGameBtn.addEventListener('click', showLobbyView);
        resetGameBtn.addEventListener('click', resetCurrentGame);
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));
        
        copyGameIdBtn.addEventListener('click', () => {
            const gameId = gameIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/chess-online-2-player/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>All New Untitled Boxing Game Codes for Free Rewards</title>
		<link>https://techaiconnect.com/all-new-untitled-boxing-game-codes-for-free-rewards/</link>
					<comments>https://techaiconnect.com/all-new-untitled-boxing-game-codes-for-free-rewards/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 17:23:00 +0000</pubDate>
				<category><![CDATA[Article]]></category>
		<category><![CDATA[Featured]]></category>
		<category><![CDATA[free cash]]></category>
		<category><![CDATA[free spins]]></category>
		<category><![CDATA[gaming rewards]]></category>
		<category><![CDATA[gaming tips]]></category>
		<category><![CDATA[Roblox boxing]]></category>
		<category><![CDATA[roblox codes]]></category>
		<category><![CDATA[Untitled Boxing Game codes]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4216</guid>

					<description><![CDATA[Discover the latest Untitled Boxing Game codes and enhance your gameplay with free spins, cash, and emotes. Redeem these codes to become stronger and defeat your opponents swiftly.]]></description>
										<content:encoded><![CDATA[<p>Update: checked for new Untitled Boxing Game codes on June 16, 2025</p>
<p>Despite its name, many Roblox players are honing their boxing skills in the Untitled Boxing Game on Roblox. The objective is straightforward: train hard, get stronger, and defeat your opponents in the ring. To assist in your journey, we have compiled all the Untitled Boxing Game codes below, offering you free spins, cash, and emotes. By utilizing these spins and cash, you can quickly enhance your strength and overcome your adversaries with just a few punches.</p>
<h2>All New Untitled Boxing Game Codes</h2>
<ul>
<li><strong>ubgliveson</strong>: 14x Lucky Spins (<strong><mark class="has-inline-color has-gradient-red-color" style="background-color:rgba(0, 0, 0, 0)">NEW</mark></strong>)</li>
<li><strong>disjoints</strong>: 5 Free Spins </li>
<li><strong>whitecash</strong>: 14,999 Cash</li>
<li><strong>joeisreal</strong>: 10 Free Spins </li>
<li><strong>vexthegoat</strong>: 15 Free Spins </li>
<li><strong>bringus</strong>: 10 Free Spins </li>
<li><strong>void</strong>: 2,999 Cash </li>
<li><strong>lovereturns</strong>: 2,999 Cash</li>
<li><strong>matchmaking</strong>: 5 Free Spins</li>
<li><strong>newyear</strong>: 2,999 Cash</li>
<li><strong>greenscreen</strong>: 10 Free Spins</li>
<li><strong>jolly2</strong>: 5 Free Spins</li>
<li><strong>morefeints</strong>: 3 Free Spins</li>
<li><strong>avatar</strong>: 4,999 Cash</li>
<li><strong>fastservers</strong>: 5 Spins</li>
<li><strong>dualemotes</strong>: Free emote</li>
<li><strong>freeemoteforall</strong>: Free emote</li>
</ul>
<h3>Expired Untitled Boxing Game Codes</h3>
<ul>
<li>knockdownfits</li>
<li>powerlevel</li>
<li>asura</li>
<li>bigbigcode</li>
<li>coolchanges</li>
<li>supersecret</li>
<li>sale</li>
<li>shotgunrework</li>
<li>weball</li>
<li>jumpscare</li>
<li>ubgforever</li>
<li>oneyear</li>
<li>500mil</li>
<li>animetime</li>
<li>animecrates</li>
<li>bigcode</li>
<li>kocash</li>
<li>manyfixes</li>
<li>ipposreturn</li>
<li>settings</li>
<li>activateboost</li>
<li>brazil</li>
<li>freeemote2</li>
<li>cashcashcash</li>
<li>watwatwat</li>
<li>balrog</li>
<li>chronos</li>
<li>thegames</li>
<li>beowulf</li>
<li>koanims</li>
<li>morecash</li>
<li>randomcode</li>
<li>freeemote</li>
<li>valentines</li>
<li>vegeta</li>
<li>hammer</li>
<li>freeemote1</li>
<li>delayingsome</li>
<li>yamcha</li>
<li>styletitles</li>
<li>nice</li>
<li>charge</li>
<li>jolly</li>
<li>200mil</li>
<li>250k</li>
<li>emotes</li>
<li>freedom</li>
<li>freecrates</li>
<li>pocketchange</li>
<li>teleport</li>
<li>funnycode</li>
<li>shotgun</li>
<li>turtle</li>
<li>trading</li>
<li>moretrading</li>
<li>ironfist</li>
<li>balance1</li>
</ul>
<p>If you are a fan of fighting games on Roblox, we recommend trying out a newly released game called MMA Legends. Additionally, there are other popular fighting games with codes like Asura and Kengan that we have experienced before. For more fresh gaming experiences, explore our Roblox codes master list.</p>
<h2>How to Redeem Untitled Boxing Game Codes</h2>
<p>Follow the steps below to redeem your Untitled Boxing Game codes:</p>
<ul>
<li>Open <a href="https://www.roblox.com/games/13621938427/MAPS-untitled-boxing-game" rel="noopener nofollow" target="_blank">Untitled Boxing Game</a> in the Roblox player.</li>
<li>Click the <strong>CODES button</strong> in the menu on the left.</li>
<li>Enter a valid code in the pop-up text box.</li>
<li>Click the <strong>REDEEM button</strong> to receive your free rewards.</li>
</ul>
<figure><img decoding="async" alt="Redeem codes option in Untitled Boxing Game" src="https://techaiconnect.com/wp-content/uploads/2025/06/Redeem-codes-option-in-Untitled-Boxing-Game.jpg"/></figure>
<h2>How to Get More Untitled Boxing Game Codes</h2>
<p>The easiest way to get the latest Untitled Boxing Game codes is to bookmark this page. It saves you the trouble of joining a Discord server or following developers on X. We frequently check for new codes and update our list as soon as a new update is available.</p>
<p>However, if you prefer, join the official <a href="https://discord.gg/ubg" rel="noopener nofollow" target="_blank">drowningsome Discord server</a> or follow their official X account (<a href="https://twitter.com/drowningsome" rel="noopener nofollow" target="_blank">@drowningsome</a>) to get the newest UBG codes.</p>
<h2>How to Get More Spins in Untitled Boxing Game?</h2>
<p>Spins are essential for leveling up in UBG. Besides using codes, there are alternative ways to acquire more spins. Collect daily rewards and complete quests to earn plenty of free spins.</p>
<p>Additionally, you can purchase spins with in-game coins (2000 per spin) or Robux. If you&#8217;re fortunate, the spins can significantly boost your strength quickly, even if you&#8217;re new to Untitled Boxing Game.</p>
<p>Moreover, let us know in the comments below if we missed any new Untitled Boxing Game codes, and we will add them right away. While you&#8217;re here, become an egotistical footballer with Meta Lock codes or the best pirate with Blox Fruit codes.</p>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/all-new-untitled-boxing-game-codes-for-free-rewards/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Find the Number Online</title>
		<link>https://techaiconnect.com/find-the-number-online/</link>
					<comments>https://techaiconnect.com/find-the-number-online/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 16:03:18 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4278</guid>

					<description><![CDATA[Tìm Số 1-100 Online Tìm Số Siêu Tốc (1-100) Ai nhanh tay nhanh mắt hơn? [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="vi">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Tìm Số 1-100 Online</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;700;900&family=Patrick+Hand&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Be Vietnam Pro', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #D1D5DB; /* text-gray-300 */
        }
        .dark-input {
            background-color: #374151; border-color: #4B5563; color: #F9FAFB;
        }
        .dark-input::placeholder { color: #6B7280; }
        .dark-input:focus { --tw-ring-color: #14b8a6; border-color: #14b8a6; }
        .modal { transition: opacity 0.25s ease; }

        /* Game-specific styles */
        #game-board {
            position: relative;
            width: 100%;
            aspect-ratio: 4 / 5;
            max-width: 800px;
            margin: auto;
            background-color: #f7f3e9;
            border-radius: 1rem;
            box-shadow: 0 0 20px rgba(0,0,0,0.2);
            overflow: hidden;
            border: 10px solid #d4c5a2;
            background-image: linear-gradient(#e7e1d0 1px, transparent 1px), linear-gradient(to right, #e7e1d0 1px, #f7f3e9 1px);
            background-size: 25px 25px;
        }

        .number-container {
            position: absolute;
            width: 50px;
            height: 50px;
            display: flex;
            align-items: center;
            justify-content: center;
            cursor: pointer;
            transition: transform 0.2s ease;
        }
        .number-container:hover {
             transform: scale(1.1);
        }
        .number-container.found {
            pointer-events: none;
        }

        .number-cell {
            font-family: 'Patrick Hand', cursive;
            font-size: clamp(18px, 4vmin, 30px);
            font-weight: bold;
            display: flex;
            align-items: center;
            justify-content: center;
            width: 40px;
            height: 40px;
            user-select: none;
            border-radius: 50%;
            transition: border-width 0.2s ease;
        }
        
        .number-cell.found-by-player {
            border-width: 3px;
            animation: found-pop 0.3s ease-out;
        }
        
        @keyframes found-pop {
            0% { transform: scale(1); }
            50% { transform: scale(1.3); }
            100% { transform: scale(1); }
        }

    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-7xl mx-auto">
        <!-- Header -->
        <header class="text-center mb-6">
            <h1 class="text-4xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-blue-500">Tìm Số Siêu Tốc (1-100)</h1>
            <p class="text-gray-400 mt-2 text-lg">Ai nhanh tay nhanh mắt hơn?</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Tên của bạn</label>
                <input type="text" id="playerName" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Nhập tên của bạn" required>
            </div>
            <!-- Host controls -->
            <div class="mb-4">
                <label for="maxPlayers" class="block mb-2 text-sm font-medium text-gray-300">Số người chơi</label>
                <select id="maxPlayers" class="dark-input w-full text-sm rounded-lg p-2.5">
                    <option value="2">2 người</option>
                    <option value="3">3 người</option>
                    <option value="4">4 người</option>
                </select>
            </div>
            <button id="createRoomBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Tạo phòng mới</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">hoặc</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinRoomId" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Nhập ID phòng (6 chữ số)">
                <button id="joinRoomBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Vào phòng</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                User ID của bạn: <span id="userIdDisplay" class="font-mono">Đang tải...</span>
            </div>
        </div>

        <!-- Game Section -->
        <main id="game-container" class="hidden">
            <div class="grid grid-cols-1 lg:grid-cols-4 gap-6">
                <!-- Left Panel: Info & Players -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 self-start">
                    <h2 class="text-2xl font-bold mb-4 text-white">Thông tin phòng</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">ID Phòng:</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="roomIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest text-gray-50"/>
                            <button id="copyRoomIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Sao chép ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>
                    <div class="mb-4">
                        <h3 class="text-lg font-bold mb-2 text-white">Bảng xếp hạng</h3>
                        <ul id="player-list" class="space-y-2 text-gray-300 max-h-60 overflow-y-auto pr-2">
                            <!-- Player list will be populated here -->
                        </ul>
                    </div>
                    <div id="game-status-display" class="mt-4 p-3 bg-gray-700 rounded-lg text-center font-semibold">
                        <!-- Game status will be shown here -->
                    </div>
                     <button id="startGameBtn" class="hidden w-full mt-4 bg-green-600 hover:bg-green-700 text-white font-bold py-2 px-4 rounded-lg transition disabled:opacity-50 disabled:cursor-not-allowed">
                        Bắt đầu chơi!
                    </button>
                     <button id="leaveRoomBtn" class="w-full mt-4 bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition">
                        Rời phòng
                    </button>
                </div>

                <!-- Middle Panel: Game Board -->
                <div class="lg:col-span-3 bg-gray-800 p-4 sm:p-6 rounded-2xl shadow-lg border border-gray-700 flex flex-col items-center justify-center">
                    <div id="game-board-container" class="w-full">
                         <div id="game-board">
                            <!-- Numbers will be populated here -->
                         </div>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Đóng</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot, arrayUnion, arrayRemove, runTransaction } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        // --- CONFIG ---
        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.appspot.com",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId || 'default-app-id';
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const createRoomBtn = document.getElementById('createRoomBtn');
        const joinRoomBtn = document.getElementById('joinRoomBtn');
        const leaveRoomBtn = document.getElementById('leaveRoomBtn');
        const copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
        const startGameBtn = document.getElementById('startGameBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const roomIdDisplay = document.getElementById('roomIdDisplay');
        const playerListEl = document.getElementById('player-list');
        const gameBoardEl = document.getElementById('game-board');
        const gameStatusDisplay = document.getElementById('game-status-display');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let userId = null;
        let currentRoomId = null;
        let unsubscribeRoom = null;
        let localPlayerName = '';
        let isHost = false;
        const playerColors = ["#ef4444", "#3b82f6", "#22c55e", "#f97316"]; // red, blue, green, orange
        let localGameState = null; // Store latest game state for optimistic updates

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userId = user.uid;
                userIdDisplay.textContent = userId;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Lỗi đăng nhập:", error);
                    showMessage("Lỗi", "Không thể xác thực người dùng. Vui lòng tải lại trang.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeRoom) {
                unsubscribeRoom();
                unsubscribeRoom = null;
            }
            currentRoomId = null;
            isHost = false;
            localGameState = null;
        }
        function showGameView(roomId) {
            currentRoomId = roomId;
            roomIdDisplay.value = roomId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }

        // --- GAME BOARD DRAWING ---
        function drawGameBoard(numbers, players) {
            gameBoardEl.innerHTML = '';
            const playerColorMap = new Map(players.map(p => [p.id, p.color]));

            numbers.forEach(num => {
                const container = document.createElement('div');
                container.className = 'number-container';
                container.style.left = num.position.left;
                container.style.top = num.position.top;
                container.style.transform = `rotate(${num.rotation}deg)`;
                // Add a data-attribute for easy selection
                container.dataset.number = num.value;

                const cell = document.createElement('div');
                cell.className = 'number-cell';
                cell.textContent = num.value;
                cell.style.color = num.color;

                if (num.foundBy) {
                    container.classList.add('found');
                    cell.classList.add('found-by-player');
                    cell.style.borderColor = playerColorMap.get(num.foundBy) || '#888';
                } else {
                    // Add both click and touch listeners for better responsiveness
                    container.onclick = () => handleNumberClick(num.value);
                    container.addEventListener('touchstart', (e) => {
                        e.preventDefault(); // Prevent firing both touch and click
                        handleNumberClick(num.value);
                    }, { passive: false });
                }
                
                container.appendChild(cell);
                gameBoardEl.appendChild(container);
            });
        }
        
        // --- FIREBASE & GAME LOGIC ---
        function getRoomDocRef(roomId) {
            return doc(db, `artifacts/${appId}/public/data/find-number-sessions/${roomId}`);
        }

        async function createNewRoom() {
            localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Lỗi", "Vui lòng nhập tên của bạn.");
                return;
            }
            if (!userId) {
                showMessage("Lỗi", "Chưa có User ID. Vui lòng chờ giây lát.");
                return;
            }

            createRoomBtn.disabled = true;
            createRoomBtn.textContent = 'Đang tạo...';

            try {
                let newRoomId;
                let roomExists = true;
                while (roomExists) {
                    newRoomId = Math.floor(100000 + Math.random() * 900000).toString();
                    const roomDocRef = getRoomDocRef(newRoomId);
                    const docSnap = await getDoc(roomDocRef);
                    roomExists = docSnap.exists();
                }

                const maxPlayers = parseInt(document.getElementById('maxPlayers').value, 10);
                const numbers = generateNumbers();

                const newRoomData = {
                    hostId: userId,
                    players: [{ id: userId, name: localPlayerName, score: 0, color: playerColors[0] }],
                    maxPlayers: maxPlayers,
                    status: 'waiting', // 'waiting', 'playing', 'finished'
                    numbers: numbers,
                    nextNumberToFind: 1,
                    createdAt: new Date().toISOString()
                };

                await setDoc(getRoomDocRef(newRoomId), newRoomData);
                isHost = true;
                listenToRoomUpdates(newRoomId);
            } catch (error) {
                console.error("Lỗi tạo phòng: ", error);
                showMessage("Lỗi", "Không thể tạo phòng. Vui lòng thử lại.");
            } finally {
                createRoomBtn.disabled = false;
                createRoomBtn.textContent = 'Tạo phòng mới';
            }
        }
        
        function generateNumbers() {
            const numbers = [];
            const textColors = ["#334155", "#1e40af", "#b91c1c", "#581c87", "#b45309", "#065f46"];
            const gridCells = [];
            const cols = 10;
            const rows = 10;
            const padding = 4; // % padding from edges to prevent cutoff

            // Create a grid of positions within a safe area
            for(let r = 0; r < rows; r++) {
                for (let c = 0; c < cols; c++) {
                    const cellWidth = (100 - padding * 2) / cols;
                    const cellHeight = (100 - padding * 2) / rows;
                    gridCells.push({
                        left: `${padding + c * cellWidth + (Math.random() * (cellWidth / 4) - cellWidth / 8)}%`,
                        top: `${padding + r * cellHeight + (Math.random() * (cellHeight / 4) - cellHeight / 8)}%`
                    });
                }
            }

            // Shuffle the grid positions
            for (let i = gridCells.length - 1; i > 0; i--) {
                const j = Math.floor(Math.random() * (i + 1));
                [gridCells[i], gridCells[j]] = [gridCells[j], gridCells[i]];
            }

            for (let i = 1; i <= 100; i++) {
                numbers.push({
                    value: i,
                    position: gridCells[i-1],
                    rotation: Math.random() * 50 - 25, // -25 to +25 degrees
                    color: textColors[Math.floor(Math.random() * textColors.length)],
                    foundBy: null
                });
            }
            return numbers;
        }

        async function joinExistingRoom() {
            localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Lỗi", "Vui lòng nhập tên của bạn.");
                return;
            }
            const roomIdToJoin = document.getElementById('joinRoomId').value.trim();
            if (!roomIdToJoin) {
                showMessage("Lỗi", "Vui lòng nhập ID phòng.");
                return;
            }

            joinRoomBtn.disabled = true;
            const roomDocRef = getRoomDocRef(roomIdToJoin);
            
            try {
                const docSnap = await getDoc(roomDocRef);
                if (!docSnap.exists()) {
                    showMessage("Lỗi", "Không tìm thấy phòng với ID này.");
                    return;
                }

                const roomData = docSnap.data();
                if (roomData.status !== 'waiting') {
                    showMessage("Lỗi", "Trò chơi đã bắt đầu hoặc kết thúc. Không thể vào phòng.");
                    return;
                }
                if (roomData.players.length >= roomData.maxPlayers) {
                    showMessage("Lỗi", "Phòng đã đầy.");
                    return;
                }
                
                if (!roomData.players.some(p => p.id === userId)) {
                    const newPlayerColor = playerColors[roomData.players.length];
                    await updateDoc(roomDocRef, {
                        players: arrayUnion({ id: userId, name: localPlayerName, score: 0, color: newPlayerColor })
                    });
                }
                isHost = false;
                listenToRoomUpdates(roomIdToJoin);
            } catch (error) {
                console.error("Lỗi vào phòng: ", error);
                showMessage("Lỗi", "Không thể vào phòng. Vui lòng kiểm tra lại ID.");
            } finally {
                joinRoomBtn.disabled = false;
            }
        }

        async function leaveCurrentRoom() {
            if (!currentRoomId || !userId) return;
            const roomDocRef = getRoomDocRef(currentRoomId);
            try {
                await runTransaction(db, async (transaction) => {
                    const sfDoc = await transaction.get(roomDocRef);
                    if (!sfDoc.exists()) return;

                    const currentPlayers = sfDoc.data().players;
                    const updatedPlayers = currentPlayers.filter(p => p.id !== userId);
                    transaction.update(roomDocRef, { players: updatedPlayers });
                });
            } catch (error) {
                console.error("Lỗi rời phòng: ", error);
            } finally {
                showLobbyView();
            }
        }

        function listenToRoomUpdates(roomId) {
            showGameView(roomId);
            const roomDocRef = getRoomDocRef(roomId);
            unsubscribeRoom = onSnapshot(roomDocRef, (docSnap) => {
                if (!docSnap.exists()) {
                    showMessage("Thông báo", "Phòng chơi đã bị đóng hoặc không tồn tại.");
                    showLobbyView();
                    return;
                }
                const roomData = docSnap.data();
                localGameState = roomData; // Keep local state in sync
                
                // Update player list & scores
                const sortedPlayers = [...roomData.players].sort((a, b) => b.score - a.score);
                playerListEl.innerHTML = '';
                sortedPlayers.forEach(p => {
                    const li = document.createElement('li');
                    li.className = `p-2 rounded-md flex justify-between items-center`;
                    li.style.backgroundColor = p.color + '33'; // Add alpha for background
                    li.innerHTML = `
                        <span class="flex items-center gap-2">
                            <span class="w-3 h-3 rounded-full" style="background-color: ${p.color};"></span>
                            <span>${p.name} ${p.id === roomData.hostId ? '👑' : ''}</span>
                        </span> 
                        <span class="font-bold text-lg">${p.score}</span>`;
                    playerListEl.appendChild(li);
                });

                // Update game board
                if (roomData.status === 'playing' || roomData.status === 'finished') {
                    drawGameBoard(roomData.numbers, roomData.players);
                } else {
                    gameBoardEl.innerHTML = `<div class="absolute inset-0 flex items-center justify-center text-gray-500 text-2xl font-bold">Đang chờ chủ phòng bắt đầu...</div>`;
                }

                // Update game status
                updateGameStatus(roomData);
                
                // Update host controls
                startGameBtn.classList.toggle('hidden', !isHost || roomData.status !== 'waiting');
                startGameBtn.disabled = roomData.players.length < 2; // Can start with 2 players
            });
        }
        
        function updateGameStatus(roomData) {
            switch(roomData.status) {
                case 'waiting':
                    gameStatusDisplay.textContent = `Đang chờ người chơi... (${roomData.players.length}/${roomData.maxPlayers})`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-blue-900/70 rounded-lg text-center font-semibold text-blue-200';
                    break;
                case 'playing':
                    gameStatusDisplay.innerHTML = `Tìm số tiếp theo: <span class="text-2xl font-black text-yellow-300">${roomData.nextNumberToFind}</span>`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-gray-700 rounded-lg text-center font-semibold';
                    break;
                case 'finished':
                    const winner = roomData.players.reduce((prev, current) => (prev.score > current.score) ? prev : current);
                    gameStatusDisplay.innerHTML = `<span class="font-bold text-yellow-400 text-lg">TRÒ CHƠI KẾT THÚC!</span><br/>Người chiến thắng: ${winner.name}`;
                    gameStatusDisplay.className = 'mt-4 p-3 bg-green-900/70 rounded-lg text-center font-semibold text-green-200';
                    break;
            }
        }
        
        async function handleStartGame() {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = getRoomDocRef(currentRoomId);
            await updateDoc(roomDocRef, { status: 'playing' });
        }
        
        async function handleNumberClick(numberValue) {
            // Check for valid local state before doing anything
            if (!currentRoomId || !userId || !localGameState || localGameState.status !== 'playing') {
                return;
            }

            // Check if the clicked number is the correct one based on the local state
            if (numberValue !== localGameState.nextNumberToFind) {
                return; // Not the right number, do nothing
            }

            // --- OPTIMISTIC UI UPDATE ---
            const player = localGameState.players.find(p => p.id === userId);
            if (!player) return;

            const targetContainer = document.querySelector(`.number-container[data-number="${numberValue}"]`);

            if (targetContainer && !targetContainer.classList.contains('found')) {
                // Instantly update the UI to feel responsive
                targetContainer.classList.add('found');
                const cell = targetContainer.querySelector('.number-cell');
                cell.classList.add('found-by-player');
                cell.style.borderColor = player.color || '#888';
                
                // Optimistically update the status display
                const nextNumber = numberValue + 1;
                if (nextNumber <= 100) {
                     gameStatusDisplay.innerHTML = `Tìm số tiếp theo: <span class="text-2xl font-black text-yellow-300">${nextNumber}</span>`;
                } else {
                     gameStatusDisplay.innerHTML = `<span class="font-bold text-yellow-400 text-lg">TRÒ CHƠI KẾT THÚC!</span>`;
                }
            }
            
            // --- FIREBASE TRANSACTION (in the background) ---
            const roomDocRef = getRoomDocRef(currentRoomId);
            try {
                await runTransaction(db, async (transaction) => {
                    const roomSnap = await transaction.get(roomDocRef);
                    if (!roomSnap.exists()) { throw "Phòng không tồn tại!"; }
                    
                    const serverRoomData = roomSnap.data();

                    // Re-validate on the server to handle race conditions
                    if (serverRoomData.status !== 'playing' || numberValue !== serverRoomData.nextNumberToFind) {
                        // Another player was faster. The optimistic update was wrong.
                        // onSnapshot will automatically correct the UI, so we just stop here.
                        return;
                    }

                    const numbers = serverRoomData.numbers;
                    const players = serverRoomData.players;
                    const numberIndex = numbers.findIndex(n => n.value === numberValue);
                    const playerIndex = players.findIndex(p => p.id === userId);

                    if (numberIndex === -1 || playerIndex === -1) return;

                    // Update the true state on the server
                    numbers[numberIndex].foundBy = userId;
                    players[playerIndex].score += 1;
                    const nextNumberOnServer = serverRoomData.nextNumberToFind + 1;
                    
                    const updatePayload = {
                        numbers: numbers,
                        players: players,
                        nextNumberToFind: nextNumberOnServer
                    };

                    if (nextNumberOnServer > 100) {
                        updatePayload.status = 'finished';
                    }

                    transaction.update(roomDocRef, updatePayload);
                });
            } catch (error) {
                console.error("Lỗi khi chọn số: ", error);
                // The UI will be corrected by the onSnapshot listener eventually.
            }
        }


        // --- EVENT LISTENERS ---
        createRoomBtn.addEventListener('click', createNewRoom);
        joinRoomBtn.addEventListener('click', joinExistingRoom);
        leaveRoomBtn.addEventListener('click', leaveCurrentRoom);
        startGameBtn.addEventListener('click', handleStartGame);
        
        copyRoomIdBtn.addEventListener('click', () => {
            const gameId = roomIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Thành công", `Đã sao chép ID phòng: ${gameId}`);
            } catch (err) {
                showMessage("Lỗi", "Không thể sao chép ID.");
            }
            document.body.removeChild(textArea);
        });
        
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/find-the-number-online/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Spin the Wheel Online (2-20 player)</title>
		<link>https://techaiconnect.com/spin-the-wheel-online/</link>
					<comments>https://techaiconnect.com/spin-the-wheel-online/#respond</comments>
		
		<dc:creator><![CDATA[techai]]></dc:creator>
		<pubDate>Sat, 05 Jul 2025 15:33:38 +0000</pubDate>
				<category><![CDATA[Game]]></category>
		<guid isPermaLink="false">https://techaiconnect.com/?p=4274</guid>

					<description><![CDATA[Lucky Wheel Online Lucky Wheel Online Share exciting moments together! Your Name Create New Room [&#8230;]]]></description>
										<content:encoded><![CDATA[<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Lucky Wheel Online</title>
    
    <!-- Tailwind CSS -->
    <script src="https://cdn.tailwindcss.com"></script>
    
    <!-- Google Fonts -->
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Be+Vietnam+Pro:wght@400;500;700;900&display=swap" rel="stylesheet">

    <style>
        body {
            font-family: 'Be Vietnam Pro', sans-serif;
            background-color: #111827; /* bg-gray-900 */
            color: #D1D5DB; /* text-gray-300 */
        }
        #wheel-container {
            position: relative;
            width: 100%;
            max-width: 450px;
            aspect-ratio: 1 / 1;
            margin: 0 auto;
        }
        #wheel-canvas {
            width: 100%;
            height: 100%;
            transition: transform 6s cubic-bezier(0.2, 0.8, 0.2, 1);
        }
        #pointer {
            position: absolute;
            top: -15px;
            left: 50%;
            transform: translateX(-50%);
            width: 0;
            height: 0;
            border-left: 25px solid transparent;
            border-right: 25px solid transparent;
            border-top: 40px solid #f59e0b; /* amber-500 */
            z-index: 10;
        }
        .dark-input {
            background-color: #374151; border-color: #4B5563; color: #F9FAFB;
        }
        .dark-input::placeholder { color: #6B7280; }
        .dark-input:focus { --tw-ring-color: #14b8a6; border-color: #14b8a6; }
        .modal { transition: opacity 0.25s ease; }
    </style>
</head>
<body class="flex items-center justify-center min-h-screen p-4">

    <div class="w-full max-w-6xl mx-auto">
        <!-- Header -->
        <header class="text-center mb-8">
            <h1 class="text-4xl md:text-5xl font-black text-transparent bg-clip-text bg-gradient-to-r from-teal-400 to-blue-500">Lucky Wheel Online</h1>
            <p class="text-gray-400 mt-2 text-lg">Share exciting moments together!</p>
        </header>

        <!-- Lobby Section -->
        <div id="lobby" class="bg-gray-800 p-8 rounded-2xl shadow-lg border border-gray-700 max-w-md mx-auto">
            <div class="mb-4">
                <label for="playerName" class="block mb-2 text-sm font-medium text-gray-300">Your Name</label>
                <input type="text" id="playerName" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Enter your name" required>
            </div>
            <button id="createRoomBtn" class="w-full text-white bg-emerald-600 hover:bg-emerald-700 focus:ring-4 focus:outline-none focus:ring-emerald-800 font-medium rounded-lg text-sm px-5 py-3 text-center mb-3">Create New Room</button>
            <div class="relative flex py-3 items-center">
                <div class="flex-grow border-t border-gray-600"></div>
                <span class="flex-shrink mx-4 text-gray-400">or</span>
                <div class="flex-grow border-t border-gray-600"></div>
            </div>
            <div class="flex gap-2">
                <input type="text" id="joinRoomId" class="dark-input w-full text-sm rounded-lg p-2.5" placeholder="Enter Room ID (6 digits)">
                <button id="joinRoomBtn" class="text-white bg-blue-600 hover:bg-blue-700 focus:ring-4 focus:outline-none focus:ring-blue-800 font-medium rounded-lg text-sm px-5 py-3">Join Room</button>
            </div>
             <div class="mt-4 text-center text-xs text-gray-500">
                Your User ID: <span id="userIdDisplay" class="font-mono">Loading...</span>
            </div>
        </div>

        <!-- Game Section -->
        <main id="game-container" class="hidden">
            <div class="grid grid-cols-1 lg:grid-cols-3 gap-8">
                <!-- Left Panel: Info & Players -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700">
                    <h2 class="text-2xl font-bold mb-4 text-white">Room Information</h2>
                    <div class="mb-4">
                        <label class="block text-sm font-medium text-gray-400">Room ID (share with friends):</label>
                        <div class="flex items-center gap-2 mt-1">
                            <input readonly id="roomIdDisplay" class="w-full bg-gray-700 font-mono text-lg p-2 rounded-md border border-gray-600 text-center tracking-widest text-gray-50"/>
                            <button id="copyRoomIdBtn" class="p-2 bg-gray-600 hover:bg-gray-500 rounded-md text-gray-300" title="Copy ID">
                                <svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg>
                            </button>
                        </div>
                    </div>
                    <div class="mb-4">
                        <h3 class="text-lg font-bold mb-2 text-white">Participants (<span id="player-count">0</span>/20)</h3>
                        <ul id="player-list" class="space-y-2 text-gray-300 max-h-60 overflow-y-auto pr-2">
                            <!-- Player list will be populated here -->
                        </ul>
                    </div>
                     <button id="leaveRoomBtn" class="w-full mt-4 bg-gray-600 hover:bg-gray-500 text-white font-bold py-2 px-4 rounded-lg transition">
                        Leave Room
                    </button>
                </div>

                <!-- Middle Panel: Wheel -->
                <div class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 flex flex-col items-center justify-center">
                    <div id="wheel-container">
                        <div id="pointer"></div>
                        <canvas id="wheel-canvas" width="450" height="450"></canvas>
                    </div>
                    <button id="spin-btn" class="mt-6 bg-gradient-to-br from-teal-500 to-blue-600 text-white font-bold py-4 px-12 rounded-xl text-xl transition-all transform hover:scale-105 shadow-lg hover:shadow-blue-500/50 disabled:opacity-50 disabled:cursor-not-allowed">
                        SPIN!
                    </button>
                    <div id="result-display" class="mt-4 text-center h-12"></div>
                </div>

                <!-- Right Panel: Customization (Host only) -->
                <div id="host-controls" class="lg:col-span-1 bg-gray-800 p-6 rounded-2xl shadow-lg border border-gray-700 hidden">
                    <h2 class="text-2xl font-bold mb-4 text-center text-white">Customize Wheel</h2>
                    <p class="text-sm text-center text-gray-400 mb-4">Only the host can change the wheel.</p>
                    
                    <div class="space-y-4">
                         <button id="use-players-btn" class="w-full bg-indigo-600 hover:bg-indigo-700 text-white font-bold py-3 px-4 rounded-lg transition">
                            Use Player List
                        </button>
                        <div>
                            <label for="custom-items-input" class="block mb-2 text-sm font-medium text-gray-300">Or enter options (one per line):</label>
                            <textarea id="custom-items-input" rows="8" class="block p-2.5 w-full text-sm text-gray-200 bg-gray-700 rounded-lg border border-gray-600 focus:ring-teal-500 focus:border-teal-500"></textarea>
                        </div>
                        <button id="update-wheel-btn" class="w-full bg-green-600 hover:bg-green-700 text-white font-bold py-3 px-4 rounded-lg transition">
                            Update Wheel
                        </button>
                    </div>
                </div>
            </div>
        </main>
    </div>

    <!-- Message Modal -->
    <div id="message-modal" class="fixed inset-0 bg-black bg-opacity-70 flex items-center justify-center p-4 z-50 hidden">
        <div class="bg-gray-800 rounded-xl shadow-2xl p-8 max-w-sm text-center border border-gray-700">
            <h2 id="modal-title" class="text-2xl font-bold mb-4"></h2>
            <p id="modal-body" class="text-gray-300 mb-6"></p>
            <button id="modal-close-btn" class="w-full text-white bg-blue-600 hover:bg-blue-700 font-medium rounded-lg text-sm px-5 py-3">Close</button>
        </div>
    </div>

    <!-- Firebase SDK -->
    <script type="module">
        import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
        import { getAuth, signInAnonymously, signInWithCustomToken, onAuthStateChanged } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
        import { getFirestore, doc, getDoc, setDoc, updateDoc, onSnapshot, arrayUnion, arrayRemove } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
        import { setLogLevel } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";

        const firebaseConfig = typeof __firebase_config !== 'undefined' 
            ? JSON.parse(__firebase_config)
            : {
                apiKey: "AIzaSyBL3USs4_BgCBM1_eFrOA0Htmjj2FWa5XM",
                authDomain: "app-and-game.firebaseapp.com",
                projectId: "app-and-game",
                storageBucket: "app-and-game.firebasestorage.app",
                messagingSenderId: "670250047171",
                appId: "1:670250047171:web:ca90d4df93674be6fef476",
                measurementId: "G-4KR5Q5RCQV"
            };
        const appId = typeof __app_id !== 'undefined' ? __app_id : firebaseConfig.projectId;
        const initialAuthToken = typeof __initial_auth_token !== 'undefined' ? __initial_auth_token : null;

        const app = initializeApp(firebaseConfig);
        const db = getFirestore(app);
        const auth = getAuth(app);
        setLogLevel('debug');

        // --- DOM ELEMENTS ---
        const lobbyEl = document.getElementById('lobby');
        const gameContainerEl = document.getElementById('game-container');
        const canvas = document.getElementById('wheel-canvas');
        const ctx = canvas.getContext('2d');
        const spinBtn = document.getElementById('spin-btn');
        const resultDisplay = document.getElementById('result-display');
        const customItemsInput = document.getElementById('custom-items-input');
        const updateWheelBtn = document.getElementById('update-wheel-btn');
        const usePlayersBtn = document.getElementById('use-players-btn');
        const createRoomBtn = document.getElementById('createRoomBtn');
        const joinRoomBtn = document.getElementById('joinRoomBtn');
        const leaveRoomBtn = document.getElementById('leaveRoomBtn');
        const copyRoomIdBtn = document.getElementById('copyRoomIdBtn');
        const userIdDisplay = document.getElementById('userIdDisplay');
        const roomIdDisplay = document.getElementById('roomIdDisplay');
        const playerListEl = document.getElementById('player-list');
        const playerCountEl = document.getElementById('player-count');
        const hostControlsEl = document.getElementById('host-controls');
        const modal = document.getElementById('message-modal');
        const modalTitle = document.getElementById('modal-title');
        const modalBody = document.getElementById('modal-body');
        const modalCloseBtn = document.getElementById('modal-close-btn');

        // --- GAME STATE ---
        let currentRoomId = null;
        let unsubscribeRoom = null;
        let isHost = false;
        let isSpinning = false;
        let lastKnownSpinTime = null;
        const colors = ["#ef4444", "#f97316", "#eab308", "#84cc16", "#22c55e", "#14b8a6", "#06b6d4", "#3b82f6", "#8b5cf6", "#d946ef", "#ec4899", "#78716c"];

        // --- AUTHENTICATION ---
        onAuthStateChanged(auth, async (user) => {
            if (user) {
                userIdDisplay.textContent = user.uid;
            } else {
                try {
                    if (initialAuthToken) {
                        await signInWithCustomToken(auth, initialAuthToken);
                    } else {
                        await signInAnonymously(auth);
                    }
                } catch (error) {
                    console.error("Login error:", error);
                    showMessage("Error", "Could not authenticate user. Please reload the page.");
                }
            }
        });

        // --- UI FUNCTIONS ---
        function showMessage(title, body) {
            modalTitle.textContent = title;
            modalBody.textContent = body;
            modal.classList.remove('hidden');
        }
        function showLobbyView() {
            gameContainerEl.classList.add('hidden');
            lobbyEl.classList.remove('hidden');
            if (unsubscribeRoom) {
                unsubscribeRoom();
                unsubscribeRoom = null;
            }
            currentRoomId = null;
            isHost = false;
        }
        function showGameView(roomId) {
            currentRoomId = roomId;
            roomIdDisplay.value = roomId;
            lobbyEl.classList.add('hidden');
            gameContainerEl.classList.remove('hidden');
        }

        // --- WHEEL DRAWING ---
        function drawWheel(segments) {
            const numSegments = segments ? segments.length : 0;
            if (numSegments === 0) {
                ctx.clearRect(0, 0, canvas.width, canvas.height);
                return;
            };
            const anglePerSegment = (2 * Math.PI) / numSegments;
            const centerX = canvas.width / 2;
            const centerY = canvas.height / 2;
            const radius = canvas.width / 2 - 10;

            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.font = '16px "Be Vietnam Pro", sans-serif';
            
            segments.forEach((segment, i) => {
                const startAngle = i * anglePerSegment;
                ctx.beginPath();
                ctx.moveTo(centerX, centerY);
                ctx.arc(centerX, centerY, radius, startAngle, startAngle + anglePerSegment);
                ctx.closePath();
                ctx.fillStyle = colors[i % colors.length];
                ctx.fill();
                ctx.strokeStyle = '#4b5563';
                ctx.lineWidth = 3;
                ctx.stroke();

                ctx.save();
                ctx.translate(centerX, centerY);
                ctx.rotate(startAngle + anglePerSegment / 2);
                ctx.textAlign = "right";
                ctx.fillStyle = "#ffffff";
                ctx.font = 'bold 14px "Be Vietnam Pro"';
                let text = segment.length > 22 ? segment.substring(0, 20) + '...' : segment;
                ctx.fillText(text, radius - 15, 0);
                ctx.restore();
            });
        }

        // --- FIREBASE & GAME LOGIC ---
        async function createNewRoom() {
            const localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            if (!auth.currentUser) {
                showMessage("Error", "User not authenticated. Please wait a moment and try again.");
                return;
            }
            const currentUserId = auth.currentUser.uid;

            createRoomBtn.disabled = true;
            createRoomBtn.textContent = 'Creating...';

            try {
                let newRoomId;
                let roomExists = true;
                while (roomExists) {
                    newRoomId = Math.floor(100000 + Math.random() * 900000).toString();
                    const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${newRoomId}`);
                    const docSnap = await getDoc(roomDocRef);
                    roomExists = docSnap.exists();
                }

                const newRoomData = {
                    hostId: currentUserId,
                    players: [{ id: currentUserId, name: localPlayerName }],
                    wheelItems: [localPlayerName],
                    status: 'waiting',
                    currentRotation: 0,
                    targetRotation: 0,
                    result: '',
                    lastSpinTimestamp: null,
                    createdAt: new Date().toISOString()
                };

                await setDoc(doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${newRoomId}`), newRoomData);
                isHost = true;
                listenToRoomUpdates(newRoomId);
            } catch (error) {
                console.error("Error creating room: ", error);
                showMessage("Error", "Could not create room. Please try again.");
            } finally {
                createRoomBtn.disabled = false;
                createRoomBtn.textContent = 'Create New Room';
            }
        }

        async function joinExistingRoom() {
            const localPlayerName = document.getElementById('playerName').value.trim();
            if (!localPlayerName) {
                showMessage("Error", "Please enter your name.");
                return;
            }
            const roomIdToJoin = document.getElementById('joinRoomId').value.trim();
            if (!roomIdToJoin) {
                showMessage("Error", "Please enter a Room ID.");
                return;
            }
            if (!auth.currentUser) {
                showMessage("Error", "User not authenticated. Please wait a moment and try again.");
                return;
            }
            const currentUserId = auth.currentUser.uid;

            joinRoomBtn.disabled = true;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${roomIdToJoin}`);
            
            try {
                const docSnap = await getDoc(roomDocRef);
                if (!docSnap.exists()) {
                    showMessage("Error", "Room with this ID not found.");
                    return;
                }

                const roomData = docSnap.data();
                if (roomData.players.length >= 20 && !roomData.players.some(p => p.id === currentUserId)) {
                    showMessage("Error", "The room is full.");
                    return;
                }
                if (!roomData.players.some(p => p.id === currentUserId)) {
                    await updateDoc(roomDocRef, {
                        players: arrayUnion({ id: currentUserId, name: localPlayerName })
                    });
                }
                isHost = (roomData.hostId === currentUserId);
                listenToRoomUpdates(roomIdToJoin);
            } catch (error) {
                console.error("Error joining room: ", error);
                showMessage("Error", "Could not join the room. Please check the ID.");
            } finally {
                joinRoomBtn.disabled = false;
            }
        }

        async function leaveCurrentRoom() {
            if (!currentRoomId || !auth.currentUser) return;
            const currentUserId = auth.currentUser.uid;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            try {
                const docSnap = await getDoc(roomDocRef);
                if (docSnap.exists()) {
                    const roomData = docSnap.data();
                    const playerToRemove = roomData.players.find(p => p.id === currentUserId);
                    if (playerToRemove) {
                         await updateDoc(roomDocRef, {
                            players: arrayRemove(playerToRemove)
                        });
                    }
                }
            } catch (error) {
                console.error("Error leaving room: ", error);
            } finally {
                showLobbyView();
            }
        }

        function listenToRoomUpdates(roomId) {
            showGameView(roomId);
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${roomId}`);
            unsubscribeRoom = onSnapshot(roomDocRef, (docSnap) => {
                if (!docSnap.exists()) {
                    showMessage("Notice", "The room has been closed or does not exist.");
                    showLobbyView();
                    return;
                }
                const roomData = docSnap.data();
                const currentUserId = auth.currentUser ? auth.currentUser.uid : null;
                isHost = (roomData.hostId === currentUserId);
                
                // Update player list
                playerListEl.innerHTML = '';
                roomData.players.forEach(p => {
                    const li = document.createElement('li');
                    li.className = `p-2 rounded-md flex justify-between items-center ${p.id === roomData.hostId ? 'bg-yellow-900/50' : 'bg-gray-700'}`;
                    li.innerHTML = `<span>${p.name}</span> ${p.id === roomData.hostId ? '<span class="text-xs font-bold text-yellow-400">HOST</span>' : ''}`;
                    playerListEl.appendChild(li);
                });
                playerCountEl.textContent = roomData.players.length;

                // Update wheel
                drawWheel(roomData.wheelItems);

                // Update host controls
                hostControlsEl.classList.toggle('hidden', !isHost);
                spinBtn.disabled = !isHost || isSpinning || !roomData.wheelItems || roomData.wheelItems.length < 2;
                
                // Handle spin animation
                if (roomData.lastSpinTimestamp && roomData.lastSpinTimestamp !== lastKnownSpinTime) {
                    isSpinning = true;
                    lastKnownSpinTime = roomData.lastSpinTimestamp;
                    spinBtn.disabled = true;
                    resultDisplay.innerHTML = '';
                    canvas.style.transform = `rotate(${roomData.targetRotation}deg)`;

                    setTimeout(() => {
                        isSpinning = false;
                        spinBtn.disabled = !isHost;
                        if (roomData.result) {
                            resultDisplay.innerHTML = `<p class="text-xl font-bold text-amber-400 animate-pulse">Result: <span class="text-2xl">${roomData.result}</span></p>`;
                        }
                    }, 6000);
                }
            });
        }
        
        async function handleSpin() {
            if (!isHost || !currentRoomId || isSpinning) return;
            
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            const docSnap = await getDoc(roomDocRef);
            if (!docSnap.exists()) return;
            const roomData = docSnap.data();

            if (!roomData.wheelItems || roomData.wheelItems.length < 2) {
                showMessage("Error", "At least 2 options are needed to spin.");
                return;
            }

            const randomSpins = Math.floor(Math.random() * 6) + 8;
            const randomAngle = Math.random() * 360;
            const currentRotation = roomData.currentRotation || 0;
            const targetRotation = currentRotation + (randomSpins * 360) + randomAngle;

            const finalAngle = targetRotation % 360;
            const winningAngle = (270 - finalAngle + 360) % 360;
            const anglePerSegment = 360 / roomData.wheelItems.length;
            const winningIndex = Math.floor(winningAngle / anglePerSegment);
            const winner = roomData.wheelItems[winningIndex];

            await updateDoc(roomDocRef, {
                targetRotation: targetRotation,
                currentRotation: finalAngle,
                lastSpinTimestamp: new Date().getTime(),
                result: winner
            });
        }

        async function updateWheelItems(newItems) {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            await updateDoc(roomDocRef, {
                wheelItems: newItems
            });
        }

        // --- EVENT LISTENERS ---
        createRoomBtn.addEventListener('click', createNewRoom);
        joinRoomBtn.addEventListener('click', joinExistingRoom);
        leaveRoomBtn.addEventListener('click', leaveCurrentRoom);
        
        updateWheelBtn.addEventListener('click', () => {
             const items = customItemsInput.value.split('\n').map(item => item.trim()).filter(item => item !== '');
             if (items.length < 2) {
                 showMessage("Note", "Please enter at least 2 options.");
                 return;
             }
             updateWheelItems(items);
        });

        usePlayersBtn.addEventListener('click', async () => {
            if (!isHost || !currentRoomId) return;
            const roomDocRef = doc(db, `artifacts/${appId}/public/data/lucky-wheel-sessions/${currentRoomId}`);
            const docSnap = await getDoc(roomDocRef);
            if(docSnap.exists()){
                const players = docSnap.data().players;
                if(players.length < 2){
                    showMessage("Note", "There must be at least 2 players in the room.");
                    return;
                }
                const playerNames = players.map(p => p.name);
                updateWheelItems(playerNames);
            }
        });

        spinBtn.addEventListener('click', handleSpin);
        
        copyRoomIdBtn.addEventListener('click', () => {
            const gameId = roomIdDisplay.value;
            const textArea = document.createElement("textarea");
            textArea.value = gameId;
            document.body.appendChild(textArea);
            textArea.select();
            try {
                document.execCommand('copy');
                showMessage("Success", `Copied Room ID: ${gameId}`);
            } catch (err) {
                showMessage("Error", "Could not copy ID.");
            }
            document.body.removeChild(textArea);
        });
        
        modalCloseBtn.addEventListener('click', () => modal.classList.add('hidden'));

    </script>
</body>
</html>
]]></content:encoded>
					
					<wfw:commentRss>https://techaiconnect.com/spin-the-wheel-online/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
