masthead icon

'Machiavelli Meter' Chat Log GitHub Copilot Logo

Pilot to Copilot

...loading...

This app was first envisioned after a visit to a sophomore history class. The students were taking a paper/pencil assessment on how "Machiavellian" they were. Naturally, we started thinking about how we could digitize this experience at Tech Novice.

This is a record of our dialog with Copilot to create this app in VSCode. We started with a template, previously developed, to make the app fit into the TNT ecosystem. With that head start, we leveraged our development process of Quest > Prompt > Analyze > Incorporate to make the app.

Date: xxx
Stage No: 1

Me:This HTML/CSS/Bootstrap5/JavaScript app is to test how 'Machiavellian' a user is based on their responses to a set of 12 questions. The attached PDF features some background, the form, and scoring directions. Please create a similar introduction/set of instructions that can be inserted in the div#introDiv

Copilot:Here’s an introduction/instructions section you can insert into `div#introDiv` to match the style and purpose of your Machiavellian test app:

Original Code:
<!-- Insert inside #introDiv -->
<p>
    Welcome to the <strong>Am I Machiavellian?</strong> self-assessment! This short quiz is designed to help you explore your own tendencies toward Machiavellian thinking—named after the Renaissance philosopher Niccolò Machiavelli, whose writings are often associated with cunning, strategy, and manipulation.
</p>
<ul>
    <li>You will be presented with <strong>12 statements</strong>. For each, select how much you agree or disagree using the provided scale.</li>
    <li>Be honest—there are no right or wrong answers. Your responses are anonymous and for your own reflection.</li>
    <li>After completing all questions, your score will be calculated and explained, giving you insight into your Machiavellian traits.</li>
</ul>
<p>
    <em>Ready to discover your Machiavellian score? Scroll down and begin the quiz!</em>
</p>

This content is clear, friendly, and matches the educational tone of your app. Let me know if you want it styled or worded differently!

Commentary:CP was 'clever' to use an unordered list for the intro. We added an additional paragraph from the original pdf, but we were pleased with this intro.

Me:In div#r1c1Div, create the form with the 12 questions with possible responses, 1-5. (1 is 'strongly disagree' and 5 is 'strongly-agree'). Use a selection menu protocol for each question. Include a 'Submit' button, wired to a function, 'findMyScore()' . I'll supply instructions for scoring shortly.

Copilot:(Generated the correct code in the correct structure, in the correct place)

Commentary:Just typing this out would be so laborious! However I noticed the questions were different from those in the PDF!

Me:I supplied a pdf with the questions. Yours are slightly different. Where did you get them?

Copilot:The 12 questions I provided are based on the widely used "Mach-IV" Machiavellianism scale, originally developed by Richard Christie and Florence L. Geis in the 1970s. This scale is in the public domain and is commonly used in psychology research and educational settings to assess Machiavellian traits. The wording I used is a standard version found in many academic and online resources.

If your PDF contains a different set of questions or specific wording, please upload or paste the exact questions from your PDF. I can then update your form to match your preferred version precisely.

Me:Ok, please complete 'findMyScore()' using the protocols followed by Richard Christie and Florence Geis in the 1970s. Include comments explaining the scoring. Have the function compute a score and an interpretation in div#r1c2Div

Copilot:(Supplied the 'Original' code below)

Original Code:
function findMyScore() {
    // Mach-IV scoring: Some items are reverse-scored.
    // Reverse-scored: 3, 4, 6, 7, 9, 10, 11
    // Direct-scored: 1, 2, 5, 8, 12

    // Get form values as integers
    const responses = [];
    for (let i = 1; i <= 12; i++) {
        const val = parseInt(document.getElementById('q' + i).value, 10);
        responses.push(val);
    }

    // Indices (0-based) of reverse-scored questions
    const reverse = [2, 3, 5, 6, 8, 9, 10];

    // Apply reverse scoring: 6 - response
    let score = 0;
    for (let i = 0; i < 12; i++) {
        if (reverse.includes(i)) {
            score += (6 - responses[i]);
        } else {
            score += responses[i];
        }
    }

    // Calculate average Mach score (range: 12 to 60, or 1.0 to 5.0 if averaged)
    const avgScore = score / 12;

    // Interpretation based on Mach-IV conventions
    let interpretation = "";
    if (avgScore >= 4) {
        interpretation = "High Machiavellianism: You tend to be pragmatic, emotionally detached, and believe that the ends justify the means.";
    } else if (avgScore >= 3 && avgScore < 4) {
        interpretation = "Moderate Machiavellianism: You balance pragmatism and principle, and may use strategy when necessary.";
    } else {
        interpretation = "Low Machiavellianism: You value honesty, trust, and morality, and are less likely to manipulate others.";
    }

    // Display result in div#r1c2Div
    const resultDiv = document.getElementById('r1c2Div');
    resultDiv.innerHTML = `
        <div class="p-3">
            <h4>Your Machiavellian Score</h4>
            <p><strong>Total Score:</strong> ${score} (Average: ${avgScore.toFixed(2)})</p>
            <p>${interpretation}</p>
            <hr>
            <p><small>Scoring: Some questions are reverse-scored according to the Mach-IV protocol. Higher scores indicate greater Machiavellian tendencies.</small></p>
        </div>
    `;
}                                    

Commentary:During field testing, it was tedious to answer the 12 questions myself and then see what happened. At first I asked Copilot to create some array data to use randomly which worked well, but eventually landed on a technique where a user could select a 'Random' response. I'll discuss this more in future entries.

Me:Given the nature of this app, what would be an appropriate tag line for it?

Copilot:Here are a few appropriate tag lines for your "Am I Machiavellian?" self-assessment app:

Suggested Tag Lines
  • How cunning are you? Discover your Machiavellian side.
  • Unmask your inner strategist.
  • Are you a master of manipulation or a model of honesty?
  • Find out where you stand on the Machiavellian scale.
  • Strategy, honesty, or somewhere in between?

A concise and inviting option:

How Machiavellian are you? Take the quiz and find out!

Let me know if you want it to sound more playful, serious, or academic!

Commentary:Ultimately, I went a different direction with the tagline but I liked Copilot's suggestions

Me:
1. In the introDiv, please put a figure with an image from images with the figcaption 'Pulling some strings' Float it to the right and make sure it is responsive as the page resizes

2. Please write the necessary code to create a radial gradient for the background of the HTML page, with a center color of #FFEFAA and an edge color of #55575E

3. What google font would go well with the theme of 'Machiavelli'?

Copilot:(Correct code for the image was inserted properly at the right location. I just had to put in the correct image file name. Look at the source code to see it's work. Other suggestions were implemented without much difficulty.)

Me:Please adjust the code so that if the form is incomplete a modal warning is launched reminding the user to answer questions that did not have selected answers. In the form, highlight those missing fields with a light red background

Copilot:(Supplied reasonable code for this prompt. Initially, when the user started selecting 'missing' questions, the red highlights were still there. Additional coaching fixed the issue. The final code is shown below)

/* Highlight missing fields */
.missing-answer {
    background-color: #ffd6d6 !important;
}

<!-- Modal for incomplete form -->
<div class="modal fade" id="incompleteModal" tabindex="-1" aria-labelledby="incompleteModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header bg-danger text-white">
        <h5 class="modal-title" id="incompleteModalLabel">Incomplete Submission</h5>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        Please answer all questions before submitting the quiz. Missing questions are highlighted in red.
      </div>
      <div class="modal-footer">
        <button type="button" class="btn btn-danger" data-bs-dismiss="modal">OK</button>
      </div>
    </div>
  </div>
</div>

function init(){
    // ...existing code...

    // Add event listeners to remove 'missing-answer' as user selects an answer
    for (let i = 1; i <= 12; i++) {
        const select = document.getElementById('q' + i);
        select.addEventListener('change', function() {
            if (select.value) {
                select.classList.remove('missing-answer');
            }
        });
    }
}//end init

function findMyScore(run = null) {
    // Mach-IV scoring: Some items are reverse-scored.
    // Reverse-scored: 3, 4, 6, 7, 9, 10, 11
    // Direct-scored: 1, 2, 5, 8, 12

    // Use sampleRun if provided, otherwise get form values
    let responses = [];
    let missing = [];
    // Remove previous highlights
    for (let i = 1; i <= 12; i++) {
        document.getElementById('q' + i).classList.remove('missing-answer');
    }

    if (run && Array.isArray(run) && run.length === 12) {
        responses = run.slice();
    } else {
        for (let i = 1; i <= 12; i++) {
            const select = document.getElementById('q' + i);
            const val = select.value;
            if (!val) {
                missing.push(i);
                select.classList.add('missing-answer');
            } else {
                select.classList.remove('missing-answer');
            }
            responses.push(parseInt(val, 10));
        }
    }

    if (missing.length > 0) {
        // Show modal warning
        var modal = new bootstrap.Modal(document.getElementById('incompleteModal'));
        modal.show();
        return;
    }

    // Indices (0-based) of reverse-scored questions
    const reverse = [2, 3, 5, 6, 8, 9, 10];

    // Apply reverse scoring: 6 - response
    let score = 0;
    for (let i = 0; i < 12; i++) {
        if (reverse.includes(i)) {
            score += (6 - responses[i]);
        } else {
            score += responses[i];
        }
    }

    // Calculate average Mach score (range: 12 to 60, or 1.0 to 5.0 if averaged)
    const avgScore = score / 12;

    // Interpretation based on Mach-IV conventions
    let interpretation = "";
    if (avgScore >= 4) {
        interpretation = "High Machiavellianism: You tend to be pragmatic, emotionally detached, and believe that the ends justify the means.";
    } else if (avgScore >= 3 && avgScore < 4) {
        interpretation = "Moderate Machiavellianism: You balance pragmatism and principle, and may use strategy when necessary.";
    } else {
        interpretation = "Low Machiavellianism: You value honesty, trust, and morality, and are less likely to manipulate others.";
    }

    // Display result in div#r1c2Div
    const resultDiv = document.getElementById('r1c2Div');
    resultDiv.innerHTML = `
        <div class="p-3">
            <h4>Your Machiavellian Score</h4>
            <p><strong>Total Score:</strong> ${score} (Average: ${avgScore.toFixed(2)})</p>
            <p>${interpretation}</p>
            <hr>
            <p><small>Scoring: Some questions are reverse-scored according to the Mach-IV protocol. Higher scores indicate greater Machiavellian tendencies.</small></p>

            <p><small>This inventory was designed by Copilot. Take the real, more extensive <a href="https://openpsychometrics.org/tests/MACH-IV/" target="_blank">Mach-IV Test</a> for comparison.</small></p>

            <p>View an <a href="assets/AmimachiavellianQuizBasedonthePrince.pdf" target="_blank">education version</a> of this test from <a href="https://www.teacherspayteachers.com/store/students-of-history" target="_blank">Teachers Pay Teachers</a>.</p>

            <p><small>Note: This is a self-assessment and should not be used for any diagnostic or clinical purposes.</small></p>
        </div>
    `;
}//end findMyScore
                                

Me:After the word 'statement:' in the form, please supply a button, 'Choose Randomly' to randomly choose a value in [1, 5] for each of the 12 questions. It should tie to a function, "chooseRandomly()'

Copilot:(Supplied reasonable code)

Commentary:Copilot provided the button and the 'chooseRandomly' function. I had to 'tweak' the button's placement. The 'chooseRandomly' code is shown below:

function chooseRandomly() {
    for (let i = 1; i <= 12; i++) {
        const select = document.getElementById('q' + i);
        // Random integer between 1 and 5
        const randomValue = Math.floor(Math.random() * 5) + 1;
        select.value = randomValue;
        select.classList.remove('missing-answer');
    }//end for
}//end chooseRandomly
                                

Me:After the form is submitted, please provide a 'Clear' button to relaunch the questionnaire and clear the results panel as well as a 'Summary' button that launches a pop-up modal that states a user's Machiavellian status and lists their responses in tabular form: Question, Answer format, with a Date/Timestamp footnote. Indicate whether or not the form had 'random' selections provided. In that popup, provide a 'Copy' button so the results may be stored elsewhere.

Copilot:(Some impressive work was done and is showcased below:)

Original Code:

<script>
    // ...existing code...

    // Store last results for summary modal
    let lastMachResult = null;
    let lastMachResponses = [];
    let lastMachRandom = false;

    function findMyScore(run = null, wasRandom = false) {
        // ...existing code up to responses collection...

        if (missing.length > 0) {
            // Show modal warning
            var modal = new bootstrap.Modal(document.getElementById('incompleteModal'));
            modal.show();
            return;
        }

        // Indices (0-based) of reverse-scored questions
        const reverse = [2, 3, 5, 6, 8, 9, 10];

        // Apply reverse scoring: 6 - response
        let score = 0;
        for (let i = 0; i < 12; i++) {
            if (reverse.includes(i)) {
                score += (6 - responses[i]);
            } else {
                score += responses[i];
            }
        }

        // Calculate average Mach score (range: 12 to 60, or 1.0 to 5.0 if averaged)
        const avgScore = score / 12;

        // Interpretation based on Mach-IV conventions
        let interpretation = "";
        if (avgScore >= 4) {
            interpretation = "High Machiavellianism: You tend to be pragmatic, emotionally detached, and believe that the ends justify the means.";
        } else if (avgScore >= 3 && avgScore < 4) {
            interpretation = "Moderate Machiavellianism: You balance pragmatism and principle, and may use strategy when necessary.";
        } else {
            interpretation = "Low Machiavellianism: You value honesty, trust, and morality, and are less likely to manipulate others.";
        }

        // Store for summary modal
        lastMachResult = {
            score,
            avgScore,
            interpretation
        };
        lastMachResponses = responses.slice();
        lastMachRandom = wasRandom;

        // Display result in div#r1c2Div
        const resultDiv = document.getElementById('r1c2Div');
        resultDiv.innerHTML = `
            <div class="p-3">
                <h4>Your Machiavellian Score</h4>
                <p><strong>Total Score:</strong> ${score} (Average: ${avgScore.toFixed(2)})</p>
                <p>${interpretation}</p>
                <hr>
                <p><small>Scoring: Some questions are reverse-scored according to the Mach-IV protocol. Higher scores indicate greater Machiavellian tendencies.</small></p>
                <p><small>This inventory was designed by Copilot. Take the real, more extensive <a href="https://openpsychometrics.org/tests/MACH-IV/" target="_blank">Mach-IV Test</a> for comparison.</small></p>
                <p>View an <a href="assets/AmimachiavellianQuizBasedonthePrince.pdf" target="_blank">education version</a> of this test from <a href="https://www.teacherspayteachers.com/store/students-of-history" target="_blank">Teachers Pay Teachers</a>.</p>
                <p><small>Note: This is a self-assessment and should not be used for any diagnostic or clinical purposes.</small></p>
                <div class="mt-3 d-flex gap-2">
                    <button type="button" class="btn btn-outline-secondary" onclick="clearMachForm()">Clear</button>
                    <button type="button" class="btn btn-outline-primary" onclick="showSummaryModal()">Summary</button>
                </div>
            </div>
        `;
    }//end findMyScore

    function clearMachForm() {
        // Reset all selects
        for (let i = 1; i <= 12; i++) {
            const select = document.getElementById('q' + i);
            select.value = "";
            select.classList.remove('missing-answer');
        }
        // Clear results panel
        document.getElementById('r1c2Div').innerHTML = "";
        // Optionally scroll to top of form
        document.getElementById('machForm').scrollIntoView({behavior: "smooth"});
    }

    function chooseRandomly() {
        for (let i = 1; i <= 12; i++) {
            const select = document.getElementById('q' + i);
            // Random integer between 1 and 5
            const randomValue = Math.floor(Math.random() * 5) + 1;
            select.value = randomValue;
            select.classList.remove('missing-answer');
        }
        // Optionally, auto-submit after random selection:
        findMyScore(null, true);
    }

    // Summary Modal logic
    function showSummaryModal() {
        if (!lastMachResult || !lastMachResponses.length) return;

        // Questions array (should match the order in the form)
        const questions = [
            "1. Never tell anyone the real reason you did something unless it is useful to do so.",
            "2. The best way to handle people is to tell them what they want to hear.",
            "3. One should take action only when sure it is morally right.",
            "4. Most people are basically good and kind.",
            "5. It is safest to assume that all people have a vicious streak and it will come out when they are given a chance.",
            "6. Honesty is the best policy in all cases.",
            "7. There is no excuse for lying to someone else.",
            "8. Generally speaking, people won’t work hard unless they’re forced to do so.",
            "9. All in all, it is better to be humble and honest than to be important and dishonest.",
            "10. When you ask someone to do something for you, it is best to give the real reasons for wanting it rather than giving reasons which carry more weight.",
            "11. Most people who get ahead in the world lead clean, moral lives.",
            "12. Anyone who completely trusts anyone else is asking for trouble."
        ];
        const answerLabels = [
            "1 - Strongly Disagree",
            "2 - Disagree",
            "3 - Neutral",
            "4 - Agree",
            "5 - Strongly Agree"
        ];

        let tableRows = "";
        for (let i = 0; i < 12; i++) {
            const ans = lastMachResponses[i];
            tableRows += `<tr><td>${questions[i]}</td><td>${answerLabels[ans-1] || ans}</td></tr>`;
        }

        const now = new Date();
        const timestamp = now.toLocaleString();

        document.getElementById('summaryStatus').textContent = lastMachResult.interpretation;
        document.getElementById('summaryRandom').textContent = lastMachRandom ? "Yes" : "No";
        document.getElementById('summaryScore').textContent = `${lastMachResult.score} (Average: ${lastMachResult.avgScore.toFixed(2)})`;
        document.getElementById('summaryTableBody').innerHTML = tableRows;
        document.getElementById('summaryTimestamp').textContent = timestamp;

        // Show modal
        var modal = new bootstrap.Modal(document.getElementById('summaryModal'));
        modal.show();
    }

    function copySummaryTable() {
        // Copy the summary modal content as text
        const status = document.getElementById('summaryStatus').textContent;
        const score = document.getElementById('summaryScore').textContent;
        const random = document.getElementById('summaryRandom').textContent;
        const timestamp = document.getElementById('summaryTimestamp').textContent;

        let text = `Machiavellian Status: ${status}\nScore: ${score}\nRandomized: ${random}\n\nResponses:\n`;
        const table = document.getElementById('summaryTableBody');
        for (let row of table.rows) {
            text += `${row.cells[0].textContent} | ${row.cells[1].textContent}\n`;
        }
        text += `\nTimestamp: ${timestamp}`;

        // Copy to clipboard
        navigator.clipboard.writeText(text).then(() => {
            // Optionally, show a quick confirmation
            document.getElementById('copySummaryBtn').textContent = "Copied!";
            setTimeout(() => {
                document.getElementById('copySummaryBtn').textContent = "Copy";
            }, 1200);
        });
    }
</script>

<!-- Summary Modal -->
<div class="modal fade" id="summaryModal" tabindex="-1" aria-labelledby="summaryModalLabel" aria-hidden="true">
  <div class="modal-dialog modal-lg modal-dialog-centered">
    <div class="modal-content">
      <div class="modal-header bg-primary text-white">
        <h5 class="modal-title" id="summaryModalLabel">Machiavellian Quiz Summary</h5>
        <button type="button" class="btn-close btn-close-white" data-bs-dismiss="modal" aria-label="Close"></button>
      </div>
      <div class="modal-body">
        <p><strong>Status:</strong> <span id="summaryStatus"></span></p>
        <p><strong>Score:</strong> <span id="summaryScore"></span></p>
        <p><strong>Randomized:</strong> <span id="summaryRandom"></span></p>
        <div class="table-responsive">
          <table class="table table-bordered table-sm align-middle">
            <thead>
              <tr>
                <th>Question</th>
                <th>Answer</th>
              </tr>
            </thead>
            <tbody id="summaryTableBody"></tbody>
          </table>
        </div>
        <p class="text-end"><small id="summaryTimestamp"></small></p>
      </div>
      <div class="modal-footer">
        <button type="button" id="copySummaryBtn" class="btn btn-outline-primary" onclick="copySummaryTable()">Copy</button>
        <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Close</button>
      </div>
    </div>
  </div>
</div>
                                        

Commentary:Very impressive! Worked pretty much 'out of the box!'

Me:Can you suggest a shorter name for this app rather than 'Am I Machiavellian?'
The burger menu needs to be a lighter color when it is revealed

Copilot:(Provided these suggestions:)

Concise Name Suggestions for Your App
  • Machiavelli Meter
  • Machiavelli Quiz
  • Machiavellian Gauge
  • Machiavelli Check
  • Machiavelli Score
  • Machiavelli Test
  • Machiavellian Index
  • Machiavelli Scale
  • Machiavelli Insight
  • Machiavelli Factor

Top pick:
Machiavelli Meter — short, memorable, and clearly communicates the app’s purpose.

Original Code:

/* Make the burger menu icon lighter when revealed */
.navbar-toggler {
    filter: brightness(2);
    border-color: #fff !important;
}

.navbar-toggler:focus,
.navbar-toggler[aria-expanded="true"] {
    background-color: #f8f9fa !important;
    border-color: #fff !important;
}

.navbar-toggler-icon {
    background-image: url("data:image/svg+xml;charset=utf8,%3Csvg viewBox='0 0 30 30' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath stroke='rgba(255,255,255,0.9)' stroke-width='2' stroke-linecap='round' stroke-miterlimit='10' d='M4 7h22M4 15h22M4 23h22'/%3E%3C/svg%3E");
}

Commentary:I'm not sure I like Copilot's solution to changing the color of the burger menu, but it works!

Me:When the page loads, it looks as if the second accordion item is selected. Why is that and how do I fix it? I don't want any accordion items selected on page loading

Copilot:The reason the second accordion item appears selected on page load is because its button is missing the collapsed class and/or its .accordion-collapse div is missing the collapse class and/or has the show class.

Check your HTML for any accordion item where the button is missing collapsed or the collapse div has show. Remove show and add collapsed to all buttons for a fully collapsed accordion on page load.

Commentary:Copilot was correct. I had to add the 'collapsed' class to the button in the second accordion item