Public Goods Game Ruleset Creator
Design the parameters for your economic experiment.
1. Game Parameters
2. Advanced Options
Ruleset Preview
Adjust the parameters to generate the ruleset preview.
Your earnings for each round will be calculated in two parts:
- Private Earnings: The amount kept in your Private Account.
- Group Earnings: The total amount contributed to the Group Account by all ${N} members is multiplied by ${a.toFixed(1)}. This total is then distributed equally among all ${N} members, regardless of their contribution.
Your total payoff for the round is:
Payoff = (E - Ci) + ( ${a.toFixed(1)} × ΣCj )
Where Ci is your contribution, and ΣCj is the total group contribution.
`;
// MPR Information
html += `Where Ci is your contribution, and ΣCj is the total group contribution.
5. Key Economic Information
- Every ${currencyUnit} you contribute yields a private return of ${symbol}${a.toFixed(2)}.
- The group earns ${symbol}${a.toFixed(2) * N} for every ${currencyUnit} contributed.
- If everyone contributes their full endowment, the highest possible round payoff is ${symbol}${maxIndividualReturn.toFixed(2)}.
- The maximum possible total return to the group is ${symbol}${maxGroupReturn.toFixed(2)}.
6. Punishment Stage
After contributions are revealed, you will enter a punishment stage. You may spend ${symbol}1 to deduct ${symbol}3 from another player's earnings. This stage is costly to both parties but allows for enforcing cooperation.
`; } if (monitoringCheckbox.checked) { html += `7. Monitoring and Communication
Before or during the contribution phase, you will have access to a communication channel to discuss strategies with your group. The actions of individuals will be ${monitoringCheckbox.checked ? 'partially observable' : 'fully anonymous'}.
`; } previewContent.innerHTML = html; } // --- Event Listeners and Initialization --- function setupListeners() { // Attach update function to all inputs ALL_INPUTS.forEach(input => { input.addEventListener(input.type === 'checkbox' ? 'change' : 'input', generateRulesetPreview); }); pdfBtn.addEventListener('click', downloadPDF); generateRulesetPreview(); // Initial load } // --- PDF Generation --- function downloadPDF() { if (typeof window.jspdf === 'undefined' || typeof window.jspdf.jsPDF === 'undefined') { alert('Error: jsPDF library not loaded.'); return; } const { jsPDF } = window.jspdf; const doc = new jsPDF(); const margin = 40; const pageWidth = doc.internal.pageSize.getWidth(); let currentY = margin; // Ensure data is fresh generateRulesetPreview(); const formData = validateAndParseInputs(); const projectName = projectNameInput.value.trim() || 'Public Goods Game'; const currencySymbol = currencyLabelInput.value.trim() === 'Dollars' ? '$' : ''; const unit = currencyLabelInput.value.trim(); const symbol = currencySymbol || ''; // --- Document Header --- doc.setFontSize(20); doc.setFont('helvetica', 'bold'); doc.text(`${projectName} Ruleset`, pageWidth / 2, currentY, { align: 'center' }); currentY += 15; doc.setFontSize(14); doc.setFont('helvetica', 'normal'); doc.text(`Official Rules for Experiment`, pageWidth / 2, currentY, { align: 'center' }); currentY += 25; // --- Parameters Table --- doc.setFontSize(12); doc.setFont('helvetica', 'bold'); doc.text("Experiment Parameters", margin, currentY); currentY += 5; const paramData = [ ["Group Size (N):", formData.N.toString(), "Initial Endowment (E):", `${symbol}${formData.E}`], ["MPR (a):", formData.a.toFixed(2), "Total Rounds:", formData.T.toString()], ["Currency Unit:", unit, "Grouping:", partneringCheckbox.checked ? 'Fixed (Partnering)' : 'Random (Strangers)'], ]; doc.autoTable({ startY: currentY, body: paramData, theme: 'striped', styles: { cellPadding: 3, fontSize: 10 }, columnStyles: { 0: { fontStyle: 'bold', fillColor: [240, 253, 245] }, 2: { fontStyle: 'bold', fillColor: [240, 253, 245] } } }); currentY = doc.autoTable.previous.finalY + 20; // --- Ruleset Content --- doc.setFontSize(12); doc.setFont('helvetica', 'bold'); // Rule 1: Introduction doc.text("1. Introduction", margin, currentY); currentY += 15; doc.setFont('helvetica', 'normal'); let text = `This experiment consists of ${formData.T} rounds, played in anonymous groups of ${formData.N} participants. You will play with the same ${formData.N} participants for all ${formData.T} rounds. (${partneringCheckbox.checked ? 'Fixed Groups' : 'Randomly re-shuffled'}).`; let lines = doc.splitTextToSize(text, pageWidth - margin * 2); doc.text(lines, margin, currentY); currentY += lines.length * 12 + 10; // Rule 2: Endowment and Contribution doc.setFont('helvetica', 'bold'); doc.text("2. Endowment & Contribution", margin, currentY); currentY += 15; doc.setFont('helvetica', 'normal'); text = `At the start of each round, you receive ${symbol}${formData.E} ${unit}. You must decide how much (from ${symbol}0 to ${symbol}${formData.E}) to contribute to a Group Account. The amount you do not contribute remains in your Private Account.`; lines = doc.splitTextToSize(text, pageWidth - margin * 2); doc.text(lines, margin, currentY); currentY += lines.length * 12 + 10; // Rule 3: Payoff Calculation doc.setFont('helvetica', 'bold'); doc.text("3. Payoff Calculation", margin, currentY); currentY += 15; doc.setFont('helvetica', 'normal'); text = `Your earnings for each round are calculated as: Private Earnings + Group Earnings. Group Earnings: The total group contribution is multiplied by ${formData.a.toFixed(1)}. This amount is distributed equally among all ${formData.N} members.`; lines = doc.splitTextToSize(text, pageWidth - margin * 2); doc.text(lines, margin, currentY); currentY += lines.length * 12 + 10; // Formula Box if (currentY + 30 > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); currentY = margin; } doc.setFont('courier', 'normal'); doc.setDrawColor(5, 150, 105); doc.setFillColor(236, 253, 245); doc.rect(margin, currentY, pageWidth - margin * 2, 40, 'FD'); doc.setTextColor(30, 41, 59); doc.text(`Payoff = (E - C\u1D62) + ( ${formData.a.toFixed(1)} \u00D7 \u03A3C\u1D62 )`, margin + 10, currentY + 20); currentY += 50; // Rule 4: Advanced Options (if selected) doc.setFont('helvetica', 'bold'); if (punishmentCheckbox.checked) { if (currentY + 30 > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); currentY = margin; } doc.text("4. Punishment Stage", margin, currentY); currentY += 15; doc.setFont('helvetica', 'normal'); text = `After contributions, you may spend ${symbol}1 to deduct ${symbol}3 from another player's earnings. This is costly to both.`; lines = doc.splitTextToSize(text, pageWidth - margin * 2); doc.text(lines, margin, currentY); currentY += lines.length * 12 + 10; } if (monitoringCheckbox.checked) { if (currentY + 30 > doc.internal.pageSize.getHeight() - margin) { doc.addPage(); currentY = margin; } doc.setFont('helvetica', 'bold'); doc.text("5. Information Access", margin, currentY); currentY += 15; doc.setFont('helvetica', 'normal'); text = `Communication is enabled to coordinate strategy. The actions of individuals will be publicly observable.`; lines = doc.splitTextToSize(text, pageWidth - margin * 2); doc.text(lines, margin, currentY); currentY += lines.length * 12 + 10; } doc.save('public-goods-ruleset.pdf'); } setupListeners(); });