Game calculator taken from Tomasz’s post
Player Dashboard
Player Controls
Current Turn: 1
Regulatory Compliance
Select Filing Status
Plan Examiner
Professionally Certified
End Turn #1
let players = [];
let currentPlayerIndex = 0;
const gamePhases = [
{ name: ‘Initial Setup’, color: ‘blue’, message: “You Are In Initial Setup” },
{ name: ‘Funding’, color: ‘green’, message: “You Are Figuring out if you need more $” },
{ name: ‘Design’, color: ‘yellow’, message: “You and your team is figuring out the design details” },
{ name: ‘Owner Review’, color: ‘orange’, message: “You Are checking your GUT to see if all Jives” },
{ name: ‘Regulatory Review’, color: ‘red’, message: “Government is snooping in your biz” },
{ name: ‘Construction’, color: ‘purple’, message: “You Are finally building” },
{ name: ‘Completion’, color: ‘gold’, message: “You Are FINISHED!!!! Was it worth it?” }
];
function updateGamePhase(player, trigger) {
let newPhaseIndex = player.currentPhaseIndex || 0;
switch(trigger) {
case ‘gameStart’:
newPhaseIndex = 0;
break;
case ‘moneyTaken’:
newPhaseIndex = Math.max(1, newPhaseIndex);
break;
case ‘architectFeePaid’:
newPhaseIndex = Math.max(2, newPhaseIndex);
break;
case ‘dobFeePaid’:
newPhaseIndex = Math.max(4, newPhaseIndex);
break;
case ‘constructionFeePaid’:
newPhaseIndex = Math.max(5, newPhaseIndex);
break;
case ‘gameFinished’:
newPhaseIndex = 6;
break;
}
if (newPhaseIndex !== player.currentPhaseIndex) {
player.currentPhaseIndex = newPhaseIndex;
displayGamePhase(player);
}
}
function displayGamePhase(player) {
const phase = gamePhases[player.currentPhaseIndex || 0];
const gamePhaseDisplay = document.getElementById(‘gamePhaseDisplay’);
gamePhaseDisplay.textContent = phase.message;
gamePhaseDisplay.style.borderColor = phase.color;
const projectTimelineDisplay = document.getElementById(‘projectTimelineDisplay’);
projectTimelineDisplay.textContent = phase.message;
projectTimelineDisplay.style.color = phase.color;
}
function initializeGamePhase(player) {
player.currentPhaseIndex = 0;
displayGamePhase(player);
}
function saveGameData() {
localStorage.setItem(‘multiPlayerGameData’, JSON.stringify({
players: players,
currentPlayerIndex: currentPlayerIndex
}));
console.log(‘Game data saved:’, players);
}
function loadGameData() {
console.log(‘Attempting to load game data…’);
const savedData = localStorage.getItem(‘multiPlayerGameData’);
if (savedData) {
console.log(‘Saved data found:’, savedData);
const parsedData = JSON.parse(savedData);
players = parsedData.players;
currentPlayerIndex = parsedData.currentPlayerIndex || 0;
updatePlayerSelect();
document.getElementById(‘playerSelect’).value = currentPlayerIndex;
displayGamePhase(players[currentPlayerIndex]);
updatePlayerDisplay();
console.log(‘Game data loaded and display updated’);
} else {
console.log(‘No saved data found’);
setupGame();
}
}
function clearAllMemory() {
localStorage.removeItem(‘multiPlayerGameData’);
players = [];
currentPlayerIndex = 0;
setupGame();
console.log(‘Memory cleared, game reset’);
}
function setupGame() {
let count = prompt(“Enter the number of players (1-10):”);
count = parseInt(count);
if (count 10 || isNaN(count)) {
alert(“Please enter a number between 1 and 10.”);
return;
}
for (let i = 1; i {
let option = document.createElement(‘option’);
option.value = index;
option.textContent = player.name;
select.appendChild(option);
});
select.value = currentPlayerIndex;
updatePlayerDisplay();
}
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, “,”);
}
function getColorClass(purse, estimatedCost) {
let ratio = purse / estimatedCost;
if (ratio > 1.1) return ‘green’;
if (ratio >= 1) return ‘orange’;
return ‘red’;
}
function updatePlayerDisplay() {
let player = players[currentPlayerIndex];
let info = document.getElementById(‘playerInfo’);
let estimatedCost = player.worktypes.reduce((sum, w) => sum + w.cost, 0);
let colorClass = getColorClass(player.purse, estimatedCost);
let designFeePercentage = estimatedCost > 0 ? (player.designFeesPaid / estimatedCost) * 100 : 0;
let designFeeColorClass = designFeePercentage < 20 ? 'green' : 'red';
let regulatoryAgencyFees = calculateRegulatoryAgencyFees(player);
let loanFees = calculateLoanFees(player);
let totalProjectCost = estimatedCost + player.designFeesPaid + regulatoryAgencyFees + loanFees;
info.innerHTML = `
Purse: $${formatNumber(player.purse)}
Estimated Project Cost: $${formatNumber(estimatedCost)}
Current Design Fee: $${formatNumber(player.designFeesPaid)} (${designFeePercentage.toFixed(2)}%)
Regulatory Agency Fees: $${formatNumber(regulatoryAgencyFees)}
Loan Fees: $${formatNumber(loanFees)}
Total Project Cost: $${formatNumber(totalProjectCost)}
Filing Status: ${player.filingStatus || ‘Not Set’}
Contractor Quality: ${player.contractor Quality || ‘Not Set’}
Construction Duration: ${player.constructionDays || ‘Not Set’} days
`;
let worktypesList = document.getElementById(‘worktypesList’);
worktypesList.innerHTML = `
Worktypes (${player.worktypes.length}/6):
${player.worktypes.slice(0, 6).map((w, i) => `${i + 1}. ${w.name}: $${formatNumber(w.cost)} `).join(”)}
`;
updateMoneySourceOptions(player);
updateTransactionHistory(player);
updateFeeAmount();
document.getElementById(‘filingStatus’).value = player.filingStatus;
document.getElementById(‘
contractor Quality’).value = player.
contractor Quality;
displayGamePhase(player);
document.getElementById(‘turnCounter’).textContent = player.turnCount;
if (designFeePercentage >= 20) {
alert(`Game over for ${player.name}. Design fees have reached or exceeded 20% of the estimated project cost.`);
endGameForPlayer(currentPlayerIndex);
}
}
// … (to be continued in the next message)
// … (continued from previous part)
function calculateRegulatoryAgencyFees(player) {
return player.transactions.reduce((sum, t) => {
if (t.source.includes(‘DOB’) || t.source.includes(‘FDNY’)) {
return sum + Math.abs(t.amount);
}
return sum;
}, 0);
}
function calculateLoanFees(player) {
return player.transactions.reduce((sum, t) => {
if (t.source === ‘Bank Fee’ || t.source === ‘Investor Fee’) {
return sum + Math.abs(t.amount);
}
return sum;
}, 0);
}
function updateTransactionHistory(player) {
let transactionList = document.getElementById(‘transactionList’);
let transactions = [];
transactions.push(`
Turn ${player.turnCount}: Current purse total: $${formatNumber(player.purse)}
`);
transactions = transactions.concat(player.transactions.map(t => {
let amount = t.amount >= 0 ? `+$${formatNumber(t.amount)}` : `-$${formatNumber(Math.abs(t.amount))}`;
let source = t.source;
let details = ”;
if (source === “Investor”) {
if (t.part) {
details = ` (Part ${t.part})`;
}
} else if (source === “Investor Fee”) {
let dayCount = t.part === 2 ? 30 : t.part === 3 ? 50 : 70;
details = ` Fee 5% (${dayCount} days)`;
} else if (source === “Bank”) {
if (t.repaymentDays) {
details = ` (${t.repaymentDays} days)`;
}
} else if (source === “Bank Fee”) {
let feePercentage;
if (Math.abs(t.amount) < 14000) {
feePercentage = "1%";
} else if (Math.abs(t.amount) <= 55000) {
feePercentage = "2%";
} else {
feePercentage = "3%";
}
details = ` Fee ${feePercentage}`;
} else if (source === "
Contractor Fee") {
details = ` (${player.constructionDays} days)`;
}
return `
= 0 ? ‘positive’ : ‘negative’}”>Turn ${t.turn}: ${amount} ${source}${details}
`;
}));
transactionList.innerHTML = transactions.join(”);
}
function updateMoneySourceOptions(player) {
let sourceSelect = document.getElementById(‘moneySource’);
let initialOption = sourceSelect.querySelector(‘option[value=”Initial”]’);
if (player.initialPurseUsed) {
if (initialOption) initialOption.remove();
} else {
if (!initialOption) {
let newOption = document.createElement(‘option’);
newOption.value = ‘Initial’;
newOption.textContent = ‘Initial Purse’;
sourceSelect.insertBefore(newOption, sourceSelect.firstChild);
}
}
toggleMoneyAmountInput();
}
function toggleMoneyAmountInput() {
let source = document.getElementById(‘moneySource’).value;
let amountInput = document.getElementById(‘moneyAmount’);
if (source === “Investor”) {
amountInput.classList.add(‘hidden’);
} else {
amountInput.classList.remove(‘hidden’);
}
}
function addWorktype() {
let player = players[currentPlayerIndex];
let name = document.getElementById(‘worktypeName’).value;
let cost = Math.floor(Number(document.getElementById(‘worktypeCost’).value));
if (name && cost > 0 && player.worktypes.length `${i + 1}. ${w.name}`).join(‘\n’);
let worktypeIndex = prompt(`Enter the number of the worktype to delete:\n${worktypeList}`) – 1;
if (worktypeIndex >= 0 && worktypeIndex `${i + 1}. ${w.name}`).join(‘\n’);
let worktypeIndex = prompt(`Enter the number of the worktype to replace:\n${worktypeList}`) – 1;
if (worktypeIndex >= 0 && worktypeIndex 0) {
player.worktypes[worktypeIndex] = {name, cost};
updatePlayerDisplay();
saveGameData();
} else {
alert(“Invalid worktype name or cost.”);
}
} else {
alert(“Invalid worktype number.”);
}
}
function addMoney() {
let player = players[currentPlayerIndex];
let source = document.getElementById(‘moneySource’).value;
let amount = Math.floor(Number(document.getElementById(‘moneyAmount’).value));
if (source === “Investor”) {
let amountCount = prompt(“How many amounts to add? (2-4)”);
if (amountCount 4) {
alert(“Invalid number of amounts. Please choose between 2 and 4.”);
return;
}
let totalAmount = 0;
let amounts = [];
for (let i = 0; i 0) {
amounts.push(partAmount);
totalAmount += partAmount;
} else {
alert(‘Please enter a valid amount.’);
return;
}
}
if (totalAmount {
player.transactions.push({amount: amount, source: “Investor”, part: index + 1, turn: player.turnCount});
});
player.transactions.push({amount: -fee, source: “Investor Fee”, part: amounts.length, turn: player.turnCount});
updateGamePhase(player, ‘moneyTaken’);
} else if (source === “Bank”) {
if (amount 4000000) {
alert(‘Bank loans must be between $1 and $4,000,000.’);
return;
}
let feeRate;
if (amount < 1400000) {
feeRate = 0.01;
} else if (amount 0) {
player.purse += amount;
if (source === “Initial”) {
player.initialPurseUsed = true;
}
player.transactions.push({amount, source, turn: player.turnCount});
} else {
alert(‘Please enter a valid amount.’);
return;
}
}
updatePlayerDisplay();
document.getElementById(‘moneyAmount’).value = ”;
saveGameData();
}
// … (to be continued in the next message)
// … (continued from previous parts)
function updateFeeAmount() {
let feeType = document.getElementById(‘feeType’).value;
let feeAmountInput = document.getElementById(‘feeAmount’);
let player = players[currentPlayerIndex];
let estimatedCost = player.worktypes.reduce((sum, w) => sum + w.cost, 0);
if (feeType.includes(“initial design fee”) ||
feeType.includes(“additional design services fee”) ||
feeType.includes(“DOB initial fee”) ||
feeType.includes(“DOB Post Approval amendment fee”) ||
feeType.includes(“FDNY initial fee”)) {
let percentage = parseInt(feeType.match(/\d+/)[0]);
let calculatedFee = Math.floor(estimatedCost * (percentage / 100));
feeAmountInput.value = calculatedFee;
feeAmountInput.disabled = true;
} else if (feeType === “
Contractor Fee”) {
let feePercentage;
switch (player.
contractor Quality) {
case ‘low’:
feePercentage = 0.18;
break;
case ‘medium’:
feePercentage = 0.19;
break;
case ‘high’:
feePercentage = 0.20;
break;
default:
alert(“Please select a
contractor quality before calculating the fee.”);
feeAmountInput.value = ”;
feeAmountInput.disabled = false;
return;
}
let calculatedFee = Math.floor(estimatedCost * feePercentage);
feeAmountInput.value = calculatedFee;
feeAmountInput.disabled = true;
} else if (feeType.startsWith(“Bypass”)) {
switch (feeType) {
case “Bypass Skip Architect”:
feeAmountInput.value = 500;
break;
case “Bypass Skip Engineer”:
feeAmountInput.value = 1000;
break;
case “Bypass Skip DOB”:
feeAmountInput.value = 1500;
break;
case “Bypass Skip FDNY”:
feeAmountInput.value = 2000;
break;
case “Bypass Caught, Low Consequences”:
feeAmountInput.value = 10000;
break;
case “Bypass Caught, High Fines”:
feeAmountInput.value = 100000;
break;
}
feeAmountInput.disabled = true;
} else {
feeAmountInput.value = ”;
feeAmountInput.disabled = false;
}
}
function deductFeeFromPlayer() {
let player = players[currentPlayerIndex];
let feeType = document.getElementById(‘feeType’).value;
let amount = Math.floor(Number(document.getElementById(‘feeAmount’).value));
if (amount player.purse) {
alert(“Invalid fee amount or insufficient funds.”);
return;
}
player.purse -= amount;
player.transactions.push({amount: -amount, source: feeType, turn: player.turnCount});
if (feeType.includes(“Architect”)) {
updateGamePhase(player, ‘architectFeePaid’);
} else if (feeType.includes(“DOB”) || feeType.includes(“FDNY”)) {
updateGamePhase(player, ‘dobFeePaid’);
} else if (feeType === “
Contractor Fee”) {
updateGamePhase(player, ‘constructionFeePaid’);
}
if (feeType.includes(“design fee”) || feeType.includes(“design services fee”)) {
player.designFeesPaid += amount;
}
if (feeType.startsWith(“Bypass”)) {
console.log(`Player used ${feeType}`);
}
updatePlayerDisplay();
saveGameData();
}
function update
Contractor Info() {
let player = players[currentPlayerIndex];
let quality = document.getElementById(‘
contractor Quality’).value;
let dieRoll = parseInt(document.getElementById(‘dieRoll’).value);
let
contractor Info = document.getElementById(‘
contractor Info’);
if (!quality || isNaN(dieRoll) || dieRoll 6) {
contractor Info.innerHTML = ‘Please select a
contractor quality and enter a valid die roll (1-6).’;
return;
}
let estimatedCost = player.worktypes.reduce((sum, w) => sum + w.cost, 0);
let
contractor Multiplier;
switch (quality) {
case ‘low’:
contractor Multiplier = 1.2;
break;
case ‘medium’:
contractor Multiplier = 1;
break;
case ‘high’:
contractor Multiplier = 0.8;
break;
}
let constructionDays = Math.floor(0.4 * Math.sqrt(estimatedCost / 1000) * dieRoll * player.worktypes.length *
contractor Multiplier);
contractor Info.innerHTML = `
Construction Duration: ${constructionDays} days
Die Roll: ${dieRoll}
`;
player.
contractor Quality = quality;
player.constructionDays = constructionDays;
saveGameData();
updatePlayerDisplay();
}
function updat
eFiling Status() {
let player = players[currentPlayerIndex];
let status = document.getElementById(‘filingStatus’).value;
player.filingStatus = status;
updatePlayerDisplay();
saveGameData();
}
function undoLastTransaction() {
let player = players[currentPlayerIndex];
if (player.transactions.length > 0) {
let lastTransaction = player.transactions.pop();
player.purse -= lastTransaction.amount;
if (lastTransaction.source.includes(‘design fee’) || lastTransaction.source.includes(‘design services fee’)) {
player.designFeesPaid += lastTransaction.amount;
}
updatePlayerDisplay();
saveGameData();
} else {
alert(“No transactions to undo.”);
}
}
function undoLastTwoTransactions() {
let player = players[currentPlayerIndex];
if (player.transactions.length >= 2) {
for (let i = 0; i player.worktypes.length === 0)) {
updateGamePhase(players[currentPlayerIndex], ‘gameFinished’);
alert(“All players have completed their projects. Game Over!”);
}
}
function endGameForPlayer(playerIndex) {
players[playerIndex].worktypes = [];
checkGameEnd();
}
function confirmAction(action) {
let message;
switch(action) {
case ‘previousPlayer’:
message = “Are you sure you want to go to the previous player?”;
break;
case ‘nextPlayer’:
message = “Are you sure you want to go to the next player?”;
break;
case ‘clearAllMemory’:
message = “Are you sure you want to clear all memory and start a new game?”;
break;
case ‘undoLastTransaction’:
message = “Are you sure you want to undo the last transaction?”;
break;
case ‘undoLastTwoTransactions’:
message = “Are you sure you want to undo the last two transactions?”;
break;
}
if (confirm(message)) {
switch(action) {
case ‘previousPlayer’:
previousPlayer();
break;
case ‘nextPlayer’:
nextPlayer();
break;
case ‘clearAllMemory’:
clearAllMemory();
break;
case ‘undoLastTransaction’:
undoLastTransaction();
break;
case ‘undoLastTwoTransactions’:
undoLastTwoTransactions();
break;
}
}
}
function previousPlayer() {
currentPlayerIndex = (currentPlayerIndex – 1 + players.length) % players.length;
updatePlayerSelect();
updatePlayerDisplay();
}
function nextPlayer() {
currentPlayerIndex = (currentPlayerIndex + 1) % players.length;
updatePlayerSelect();
updatePlayerDisplay();
}
function endTurn() {
players[currentPlayerIndex].turnCount++;
currentPlayerIndex = (currentPlayerIndex + 1) % players.length;
updatePlayerSelect();
updatePlayerDisplay();
saveGameData();
}
function init
Expeditor Cards() {
document.getElementById(‘
expeditor CardTarget’).addEventListener(‘change’, function() {
const targetPlayerSelect = document.getElementById(‘
expeditor CardTargetPlayer’);
targetPlayerSelect.classList.toggle(‘hidden’, this.value !== ‘other’);
});
const targetPlayerSelect = document.getElementById(‘
expeditor CardTargetPlayer’);
players.forEach((player, index) => {
let option = document.createElement(‘option’);
option.value = index;
option.textContent = player.name;
targetPlayerSelect.appendChild(option);
});
}
function apply
Expeditor Card() {
const cardType = document.getElementById(‘
expeditor CardType’).value;
const cardValue = parseFloat(document.getElementById(‘
expeditor CardValue’).value);
const cardTarget = document.getElementById(‘
expeditor CardTarget’).value;
const cardDescription = document.getElementById(‘
expeditor CardDescription’).value;
if (isNaN(cardValue) || cardValue applyCardToPlayer(player, cardType, cardValue, cardDescription));
}
updatePlayerDisplay();
saveGameData();
}
function applyCardToPlayer(player, cardType, cardValue, description) {
if (cardType === ‘fixed’) {
player.purse -= cardValue;
player.transactions.push({
amount: -cardValue,
source: `
Expeditor Card: ${description}`,
turn: player.turnCount
});
} else if (cardType === ‘percentage’) {
const estimatedCost = player.worktypes.reduce((sum, w) => sum + w.cost, 0);
const effect = Math.floor(estimatedCost * (cardValue / 100));
player.purse -= effect;
player.transactions.push({
amount: -effect,
source: `
Expeditor Card: ${description} (${cardValue}% of ${estimatedCost})`,
turn: player.turnCount
});
}
}
function toggleGameControls() {
const gameControls = document.querySelector(‘.game-controls’);
gameControls.classList.toggle(‘visible’);
}
// Initialize the game
window.addEventListener(‘load’, () => {
loadGameData();
init
Expeditor Cards();
});