🌍 African Mancala (Hard Mode) – Wagers, Tax & Payouts 🌍
Authentic Oware Game with Traditional Wooden Board & Seeds
Tax: 16% • Winner: 75% • House: 25%
Ready to play! Set your wager and start the game.
Rules: sow seeds counter-clockwise, skipping opponent's store.
Last seed in own store → extra turn.
Last seed in empty pit on own side → capture + opposite seeds.
${createSeedVisuals(seedCount)}
${seedCount}
`;
} else {
d.innerHTML = `0
`;
}
d.onclick=()=>onPitClick(i,owner);
return d;
}
function storeEl(i,name){
const d=document.createElement("div");
d.className="store";
const seedCount = board[i];
if(seedCount > 0) {
d.innerHTML = `${createSeedVisuals(Math.min(seedCount, 12))}
${seedCount}
`;
} else {
d.innerHTML = `0
`;
}
return d;
}
function createSeedVisuals(count) {
const maxVisible = count > 8 ? 8 : count;
let seedsHtml = '';
for(let i = 0; i < maxVisible; i++) {
const rotation = Math.random() * 360;
const offsetX = Math.random() * 20 - 10;
const offsetY = Math.random() * 20 - 10;
seedsHtml += ``;
}
return seedsHtml;
}
function isGameOver(){
return [0,1,2,3,4,5].every(i=>board[i]===0)||[7,8,9,10,11,12].every(i=>board[i]===0);
}
function collectRemaining(){
let p1=[0,1,2,3,4,5].reduce((a,i)=>a+board[i],0);
let p2=[7,8,9,10,11,12].reduce((a,i)=>a+board[i],0);
board[6]+=p1;
board[13]+=p2;
for(let i of [0,1,2,3,4,5])board[i]=0;
for(let i of [7,8,9,10,11,12])board[i]=0;
}
function makeMove(from,pl){
return new Promise((resolve) => {
let seeds=board[from];
board[from]=0;
let i=from;
animateSeedMovement(from, seeds, pl, () => {
const ownStore=(pl===0?6:13);
const ownSide=(pl===0?[0,1,2,3,4,5]:[7,8,9,10,11,12]);
if(ownSide.includes(i)&&board[i]===1){
let opp=12-i;
if(board[opp]>0){
board[ownStore]+=board[opp]+1;
board[i]=0;
board[opp]=0;
renderBoard();
}
}
if(isGameOver())collectRemaining();
resolve(i===ownStore);
});
while(seeds>0){
i=(i+1)%14;
if(pl===0&&i===13)continue;
if(pl===1&&i===6)continue;
board[i]++;
seeds--;
}
});
}
function animateSeedMovement(startPit, seedCount, player, callback) {
let currentPit = startPit;
let remainingSeeds = seedCount;
function animateNextSeed() {
if (remainingSeeds <= 0) {
callback();
return;
}
do {
currentPit = (currentPit + 1) % 14;
if(player === 0 && currentPit === 13) continue;
if(player === 1 && currentPit === 6) continue;
break;
} while(true);
createMovingSeedAnimation(startPit, currentPit, () => {
renderBoard();
remainingSeeds--;
setTimeout(animateNextSeed, 200);
});
}
renderBoard();
setTimeout(animateNextSeed, 300);
}
function createMovingSeedAnimation(fromPit, toPit, callback) {
const fromElement = document.querySelector(`#board .pit:nth-child(${getPitDisplayIndex(fromPit) + 1}), #board .store:nth-child(${getPitDisplayIndex(fromPit) + 1})`);
const toElement = document.querySelector(`#board .pit:nth-child(${getPitDisplayIndex(toPit) + 1}), #board .store:nth-child(${getPitDisplayIndex(toPit) + 1})`);
if (!fromElement || !toElement) {
callback();
return;
}
const animatedSeed = document.createElement('div');
animatedSeed.className = 'animated-seed';
animatedSeed.style.cssText = `
position: absolute;
width: 12px;
height: 12px;
background: radial-gradient(circle at 30% 30%, #deb887, #a0522d, #8b4513, #654321);
border-radius: 50%;
box-shadow:
inset 2px 2px 4px rgba(255,255,255,0.4),
inset -2px -2px 4px rgba(0,0,0,0.4),
0 2px 6px rgba(0,0,0,0.4);
z-index: 100;
pointer-events: none;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
`;
const fromRect = fromElement.getBoundingClientRect();
const toRect = toElement.getBoundingClientRect();
const boardRect = document.querySelector('#board').getBoundingClientRect();
animatedSeed.style.left = (fromRect.left - boardRect.left + fromRect.width/2 - 6) + 'px';
animatedSeed.style.top = (fromRect.top - boardRect.top + fromRect.height/2 - 6) + 'px';
document.querySelector('#board').appendChild(animatedSeed);
setTimeout(() => {
animatedSeed.style.left = (toRect.left - boardRect.left + toRect.width/2 - 6) + 'px';
animatedSeed.style.top = (toRect.top - boardRect.top + toRect.height/2 - 6) + 'px';
animatedSeed.style.transform = 'scale(1.2)';
}, 10);
setTimeout(() => {
animatedSeed.remove();
callback();
}, 450);
}
function getPitDisplayIndex(pitIndex) {
const displayOrder = [13, 12, 11, 10, 9, 8, 7, 6, -1, 0, 1, 2, 3, 4, 5, -1];
return displayOrder.indexOf(pitIndex);
}
function systemChooseMove(){
const pits=[12,11,10,9,8,7].filter(i=>board[i]>0);
if(pits.length===0)return null;
let best=pits[0], bestScore=-Infinity;
for(let pit of pits){
let snapshot=board.slice();
let seeds=snapshot[pit];
snapshot[pit]=0;
let idx=pit;
while(seeds>0){
idx=(idx+1)%14;
if(idx===6)continue;
snapshot[idx]++;
seeds--;
}
let score=snapshot[13]-snapshot[6];
if(idx===13)score+=2;
if(score>bestScore){bestScore=score;best=pit;}
}
return best;
}
function computePayouts(winner){
const isOnline=(mode==="online");
const isSingle=(mode==="single");
const gross=isOnline?(wagers.p1+wagers.p2):(wagers.p1*2); // System matches wager in single mode
const tax=gross*TAX_RATE;
const net=gross-tax;
let winnerText="",wPay=0,hPay=net*HOUSE_SHARE;
if(winner==="Draw"){
winnerText="Draw";
return{gross,tax,net,winnerText,winnerPayout:0,housePayout:hPay};
}
if(isOnline){
wPay=net*WINNER_SHARE;
winnerText=winner+" Wins";
}
else{ // Single player mode
if(winner==="P1"){
wPay=net*WINNER_SHARE;
winnerText="You Win";
}else{
hPay=net; // House takes all if system wins
winnerText="System Wins";
}
}
return{gross,tax,net,winnerText,winnerPayout:wPay,housePayout:hPay};
}
function updatePayoutPanel(p){
$("#p1w").textContent=wagers.p1?fmt(wagers.p1):"—";
if(mode==="online"){
$("#p2wRow").style.display="";
$("#p2Label").textContent="Player 2 Wager";
$("#p2w").textContent=wagers.p2?fmt(wagers.p2):"—";
}
else if(mode==="single"){
$("#p2wRow").style.display="";
$("#p2Label").textContent="System Wager";
$("#p2w").textContent=wagers.p1?fmt(wagers.p1):"—";
}
else{
$("#p2wRow").style.display="none";
}
$("#grossPot").textContent=p?fmt(p.gross):"—";
$("#taxAmt").textContent=p?fmt(p.tax):"—";
$("#netPot").textContent=p?fmt(p.net):"—";
$("#winnerPay").textContent=p?fmt(p.winnerPayout):"—";
$("#housePay").textContent=p?fmt(p.housePayout):"—";
$("#result").textContent=p?p.winnerText:"—";
}
function startSinglePlayerGame(){
let w1=+$("#wager1").value;
if(!w1 || w1<100)return setMsg("Enter valid wager (minimum KSh 100)",true);
wagers={p1:w1,p2:w1}; // System matches the wager
mode="single";
initBoard();
turn=0;
gameActive=true;
// Show system match info
$("#systemMatchInfo").style.display="block";
$("#systemPotAmount").textContent=fmt(w1*2);
renderBoard();
setMsg("Game started! You vs System. System matched your wager.",false);
updatePayoutPanel(null);
// Hide online controls
$("#online-controls").style.display="none";
$("#single-controls").style.display="block";
}
function generateOnlineGame(){
let w1=+$("#wager1").value;
if(!w1 || w1<100)return setMsg("Enter valid wager (minimum KSh 100)",true);
// Simulate deposit processing
setMsg("💳 Processing deposit...",false);
setTimeout(()=>{
gameRoom = createGameRoom(w1);
if (!gameRoom) {
setMsg("Failed to create game room. Please try again.", true);
return;
}
playerRole = "host";
wagers={p1:w1,p2:0};
// Generate shareable game URL - fallback for iframe environments
let gameUrl;
try {
gameUrl = `${window.location.origin}${window.location.pathname}?room=${gameRoom.id}`;
} catch (e) {
// Fallback URL for iframe environments
gameUrl = `https://mancala-game.example.com/play?room=${gameRoom.id}`;
}
$("#shareLink").value = gameUrl;
$("#currentRoomId").textContent = gameRoom.id;
// Update UI
$("#generateLinkBtn").style.display="none";
$("#link-display").style.display="block";
$("#gameRoomInfo").style.display="block";
$("#systemMatchInfo").style.display="none";
updateUrl(gameRoom.id);
startExpiryTimer();
setupSocialSharing(gameUrl, w1);
setMsg("✅ Deposit successful! Share the link to invite opponent.",false);
},1500);
}
function startExpiryTimer(){
if(expiryTimer) clearInterval(expiryTimer);
expiryTimer = setInterval(()=>{
if(!gameRoom) return;
const timeLeft = gameRoom.expiresAt - Date.now();
if(timeLeft <= 0){
clearInterval(expiryTimer);
$("#linkStatus").textContent="❌ Link Expired";
$("#linkStatus").className="status-expired";
$("#linkExpiry").textContent="";
$("#cancelGameBtn").textContent="🔄 Create New Game";
setMsg("Link expired. Create a new game to play.",true);
return;
}
const minutes = Math.floor(timeLeft / 60000);
const seconds = Math.floor((timeLeft % 60000) / 1000);
$("#linkExpiry").textContent=`Expires in: ${minutes}:${seconds.toString().padStart(2,'0')}`;
},1000);
}
function setupSocialSharing(gameUrl, wager){
const message = `🎮 Join me for African Mancala!
Wager: KSh ${wager}
Winner takes 75% of pot after tax
Room ID: ${gameRoom.id}
${gameUrl}`;
const encodedMessage = encodeURIComponent(message);
const encodedUrl = encodeURIComponent(gameUrl);
// Social media sharing with fallback URLs
$("#whatsappShare").href = `https://wa.me/?text=${encodedMessage}`;
$("#twitterShare").href = `https://twitter.com/intent/tweet?text=${encodedMessage}`;
$("#telegramShare").href = `https://t.me/share/url?url=${encodedUrl}&text=${encodeURIComponent('🎮 Join me for African Mancala! Room: '+gameRoom.id+' | Wager: KSh '+wager)}`;
$("#facebookShare").href = `https://www.facebook.com/sharer/sharer.php?u=${encodedUrl}`;
$("#smsShare").onclick = ()=>{
try {
if (navigator.share) {
navigator.share({
title: 'African Mancala Game',
text: message,
url: gameUrl
});
} else {
const smsUrl = `sms:?body=${encodedMessage}`;
window.open(smsUrl);
}
} catch (e) {
// Fallback - copy to clipboard
try {
navigator.clipboard.writeText(message);
setMsg("📱 Message copied to clipboard!", false);
} catch (clipErr) {
setMsg("📱 Copy the room ID: " + gameRoom.id, false);
}
}
};
}
function joinOnlineGame(roomId, guestWager){
const result = joinGameRoom(roomId, guestWager);
if(!result.success){
setMsg(`❌ ${result.message}`,true);
return;
}
gameRoom = result.room;
playerRole = "guest";
wagers = {p1: gameRoom.host.wager, p2: guestWager};
// Start the game immediately
initBoard();
turn = 0;
gameActive = true;
mode = "online";
$("#gameRoomInfo").style.display="block";
$("#currentRoomId").textContent = roomId;
$("#hostStatus").textContent = "Connected";
$("#guestStatus").textContent = "Connected (You)";
renderBoard();
updatePayoutPanel(null);
setMsg("✅ Joined game successfully! Game started.",false);
// Hide join controls
$("#joinRoomControls").style.display="none";
$("#toggleJoinBtn").style.display="none";
$("#online-controls").style.display="none";
}
function cancelOnlineGame(){
if(gameRoom && playerRole === "host"){
if(cancelGameRoom(gameRoom.id)){
setMsg("💰 Game cancelled. Deposit refunded.",false);
}
}
// Reset UI
$("#generateLinkBtn").style.display="block";
$("#link-display").style.display="none";
$("#gameRoomInfo").style.display="none";
if(expiryTimer) clearInterval(expiryTimer);
gameRoom = null;
playerRole = null;
updateUrl();
}
function endGame(){
gameActive=false;
renderBoard();
let p1=board[6],p2=board[13];
let winner="Draw";
if(p1>p2)winner="P1";
else if(p2>p1)winner=(mode==="single"?"System":"P2");
const pay=computePayouts(winner);
updatePayoutPanel(pay);
showResultsPanel(p1, p2, winner, pay);
setMsg("Game Over: "+pay.winnerText,false);
// Clean up online game
if(gameRoom) {
gameRoom.status = "finished";
saveGameRoom(gameRoom.id, gameRoom);
if(expiryTimer) clearInterval(expiryTimer);
}
}
function showResultsPanel(p1Score, p2Score, winner, payouts) {
const resultsPanel = $("#resultsPanel");
const p2Label = mode === "single" ? "System" : "Player 2";
$("#p1Score").textContent = p1Score;
$("#p2Score").textContent = p2Score;
$("#p2ScoreLabel").textContent = p2Label;
let resultText = "";
let celebrationText = "";
let payoutText = "";
if (winner === "Draw") {
resultText = "🤝 It's a Draw!";
celebrationText = "🤝 Great Game!";
payoutText = "No winner - House takes commission";
$("#winnerCelebration").style.background = "linear-gradient(145deg, rgba(245, 158, 11, 0.2), rgba(245, 158, 11, 0.1))";
} else if (winner === "P1") {
resultText = "🎉 Player 1 Wins!";
celebrationText = "🎉 Congratulations! 🎉";
payoutText = `You win ${fmt(payouts.winnerPayout)}!`;
$("#winnerCelebration").style.background = "linear-gradient(145deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.1))";
} else if (winner === "System") {
resultText = "🤖 System Wins!";
celebrationText = "🤖 System Victory!";
payoutText = "Better luck next time!";
$("#winnerCelebration").style.background = "linear-gradient(145deg, rgba(239, 68, 68, 0.2), rgba(239, 68, 68, 0.1))";
} else {
resultText = "🎉 Player 2 Wins!";
celebrationText = "🎉 Congratulations Player 2! 🎉";
payoutText = playerRole === "guest" ? `You win ${fmt(payouts.winnerPayout)}!` : `Player 2 wins ${fmt(payouts.winnerPayout)}!`;
$("#winnerCelebration").style.background = "linear-gradient(145deg, rgba(34, 197, 94, 0.2), rgba(34, 197, 94, 0.1))";
}
$("#gameResult").textContent = resultText;
$("#celebrationText").textContent = celebrationText;
$("#payoutAmount").textContent = payoutText;
$("#totalWagered").textContent = fmt(payouts.gross);
$("#taxDeducted").textContent = `-${fmt(payouts.tax)}`;
$("#netPool").textContent = fmt(payouts.net);
$("#winnerAmount").textContent = fmt(payouts.winnerPayout);
$("#houseAmount").textContent = fmt(payouts.housePayout);
if (winner === "Draw") {
$("#winnerLabel").textContent = "No Winner:";
$("#loserRow").style.display = "none";
} else {
$("#winnerLabel").textContent = `${payouts.winnerText} (75%):`;
$("#loserRow").style.display = "flex";
}
resultsPanel.style.display = "block";
setTimeout(() => {
resultsPanel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
}, 100);
}
function startNewGame() {
$("#resultsPanel").style.display = "none";
$("#wager1").value = "";
$("#mode").value = "single";
// Reset all UI elements
$("#online-controls").style.display = "none";
$("#single-controls").style.display = "block";
$("#gameRoomInfo").style.display = "none";
$("#systemMatchInfo").style.display = "none";
$("#link-display").style.display = "none";
$("#generateLinkBtn").style.display = "block";
$("#joinRoomControls").style.display = "none";
$("#toggleJoinBtn").style.display = "none";
// Clean up
if(expiryTimer) clearInterval(expiryTimer);
gameRoom = null;
playerRole = null;
wagers = {p1: 0, p2: 0};
initBoard();
gameActive = false;
turn = 0;
mode = "single";
renderBoard();
updatePayoutPanel(null);
setMsg("Ready for a new game! Set your wager and start playing.", false);
updateUrl();
window.scrollTo({ top: 0, behavior: 'smooth' });
}
async function onPitClick(i,owner){
if(!gameActive||turn!==owner||board[i]===0)return;
let extra = await makeMove(i,owner);
renderBoard();
if(isGameOver())return endGame();
if(!extra){
turn=1-turn;
renderBoard();
if(mode==="single"&&turn===1)setTimeout(systemTurn,800);
}
}
async function systemTurn(){
if(!gameActive)return;
let m=systemChooseMove();
if(m===null)return endGame();
let extra = await makeMove(m,1);
renderBoard();
if(isGameOver())return endGame();
if(!extra){
turn=0;
renderBoard();
}else {
setTimeout(systemTurn,800);
}
}
function setMsg(t,err){
$("#message").textContent=t;
$("#message").style.color=err?"#ef4444":"#22c55e";
}
// Event Listeners
$("#mode").onchange = function(e) {
const isOnline = e.target.value === "online";
$("#online-controls").style.display = isOnline ? "block" : "none";
$("#single-controls").style.display = isOnline ? "none" : "block";
$("#systemMatchInfo").style.display = "none";
if (isOnline) {
$("#toggleJoinBtn").style.display = "block";
} else {
$("#toggleJoinBtn").style.display = "none";
$("#joinRoomControls").style.display = "none";
}
};
$("#startBtn").onclick = startSinglePlayerGame;
$("#generateLinkBtn").onclick = generateOnlineGame;
$("#cancelGameBtn").onclick = function() {
if ($("#cancelGameBtn").textContent.includes("Create New")) {
// Reset for new game
cancelOnlineGame();
setMsg("Ready to create a new game!", false);
} else {
cancelOnlineGame();
}
};
$("#copyLinkBtn").onclick = function() {
const shareLink = $("#shareLink");
shareLink.select();
shareLink.setSelectionRange(0, 99999);
try {
document.execCommand('copy');
$("#copyLinkBtn").textContent = "✅ Copied!";
setTimeout(() => {
$("#copyLinkBtn").textContent = "📋 Copy";
}, 2000);
} catch (err) {
setMsg("Failed to copy link", true);
}
};
$("#toggleJoinBtn").onclick = function() {
const joinControls = $("#joinRoomControls");
if (joinControls.style.display === "none" || !joinControls.style.display) {
joinControls.style.display = "grid";
$("#toggleJoinBtn").textContent = "❌ Cancel Join";
$("#generateLinkBtn").style.display = "none";
} else {
joinControls.style.display = "none";
$("#toggleJoinBtn").textContent = "🔗 Have a Room ID?";
$("#generateLinkBtn").style.display = "block";
}
};
$("#joinRoomBtn").onclick = function() {
const roomId = $("#roomIdInput").value.trim().toUpperCase();
const wager = +$("#wager1").value;
if (!roomId) {
setMsg("Enter a room ID", true);
return;
}
if (!wager || wager < 100) {
setMsg("Enter a valid wager amount", true);
return;
}
setMsg("🔍 Looking for game room...", false);
setTimeout(() => {
joinOnlineGame(roomId, wager);
}, 1000);
};
$("#roomIdInput").oninput = function(e) {
e.target.value = e.target.value.toUpperCase();
};
$("#resetBtn").onclick = function() {
if (gameActive) {
if (!confirm("Are you sure you want to reset the active game?")) {
return;
}
}
// Clean up any active online game
if (gameRoom && playerRole === "host" && gameRoom.status === "waiting") {
cancelGameRoom(gameRoom.id);
}
if (expiryTimer) clearInterval(expiryTimer);
// Reset all state
wagers = {p1: 0, p2: 0};
gameRoom = null;
playerRole = null;
initBoard();
gameActive = false;
turn = 0;
mode = "single";
// Reset UI
$("#mode").value = "single";
$("#wager1").value = "";
$("#online-controls").style.display = "none";
$("#single-controls").style.display = "block";
$("#gameRoomInfo").style.display = "none";
$("#systemMatchInfo").style.display = "none";
$("#resultsPanel").style.display = "none";
$("#link-display").style.display = "none";
$("#generateLinkBtn").style.display = "block";
$("#joinRoomControls").style.display = "none";
$("#toggleJoinBtn").style.display = "none";
$("#roomIdInput").value = "";
renderBoard();
updatePayoutPanel(null);
setMsg("Game reset. Ready to play!", false);
updateUrl();
};
// Initialize on page load
window.onload = function() {
try {
// Check if there's a room ID in URL or session storage
let roomId = getUrlParameter('room');
// Fallback to session storage for iframe environments
if (!roomId) {
try {
roomId = sessionStorage.getItem('currentRoomId');
} catch (e) {
console.log('Session storage not available');
}
}
if (roomId) {
// Auto-switch to online mode and show join controls
$("#mode").value = "online";
$("#online-controls").style.display = "block";
$("#single-controls").style.display = "none";
$("#toggleJoinBtn").style.display = "block";
$("#roomIdInput").value = roomId;
$("#joinRoomControls").style.display = "grid";
$("#generateLinkBtn").style.display = "none";
$("#toggleJoinBtn").textContent = "❌ Cancel Join";
setMsg(`🔗 Room ${roomId} found! Enter your wager and click Join.`, false);
} else {
initBoard();
renderBoard();
setMsg("Ready to play! Set your wager and start the game.", false);
}
} catch (e) {
console.log('Initialization error handled:', e);
// Default initialization
initBoard();
renderBoard();
setMsg("Ready to play! Set your wager and start the game.", false);
}
updatePayoutPanel(null);
};
// Cleanup on page unload
window.onbeforeunload = function() {
if (expiryTimer) clearInterval(expiryTimer);
// Clean up any waiting rooms
if (gameRoom && playerRole === "host" && gameRoom.status === "waiting") {
cancelGameRoom(gameRoom.id);
}
};