|
| 1 | +import React, { useState, useEffect } from 'react'; |
| 2 | +import { Link } from 'react-router-dom'; |
| 3 | +import { |
| 4 | + ArrowLeftIcon, |
| 5 | + ArrowsClockwiseIcon, |
| 6 | + BrainIcon, |
| 7 | + CheckCircleIcon |
| 8 | +} from '@phosphor-icons/react'; |
| 9 | +import Seo from '../../components/Seo'; |
| 10 | +import BreadcrumbTitle from '../../components/BreadcrumbTitle'; |
| 11 | + |
| 12 | +const FALLACIES = [ |
| 13 | + { name: 'Ad Hominem', desc: 'Attacking the person instead of the argument.' }, |
| 14 | + { name: 'Straw Man', desc: 'Misrepresenting an argument to make it easier to attack.' }, |
| 15 | + { name: 'Slippery Slope', desc: 'Assuming a small step leads to a chain of extreme events.' }, |
| 16 | + { name: 'False Dilemma', desc: 'Presenting only two options when more exist.' }, |
| 17 | + { name: 'Post Hoc', desc: 'Assuming A caused B because A happened first.' }, |
| 18 | + { name: 'Whataboutism', desc: 'Deflecting by bringing up a different issue (Tu Quoque).' }, |
| 19 | + { name: 'Red Herring', desc: 'Introducing an irrelevant topic to distract.' }, |
| 20 | + { name: 'Confirmation Bias', desc: 'Favoring info that confirms existing beliefs.' }, |
| 21 | + { name: 'Sunk Cost', desc: 'Continuing because of past investment, not future value.' }, |
| 22 | + { name: 'Dunning-Kruger', desc: 'Overestimating ability when knowledge is low.' }, |
| 23 | + { name: 'No True Scotsman', desc: 'Changing the definition to exclude counter-examples.' }, |
| 24 | + { name: 'Texas Sharpshooter', desc: 'Cherry-picking data to fit a pattern.' }, |
| 25 | + { name: 'Moving Goalposts', desc: 'Changing the criteria for proof after evidence is met.' }, |
| 26 | + { name: 'Begging the Question', desc: 'The premise assumes the conclusion is true.' }, |
| 27 | + { name: 'Appeal to Authority', desc: 'Saying it is true because an "expert" said so.' }, |
| 28 | + { name: 'Appeal to Emotion', desc: 'Manipulating emotions instead of using logic.' }, |
| 29 | + { name: 'Bandwagon', desc: 'Saying it is true because many people believe it.' }, |
| 30 | + { name: 'Appeal to Ignorance', desc: 'Assuming true because not proven false (or vice versa).' }, |
| 31 | + { name: 'Burden of Proof', desc: 'Making a claim but expecting others to disprove it.' }, |
| 32 | + { name: 'Personal Incredulity', desc: 'Saying it is false because it is hard to understand.' }, |
| 33 | + { name: 'Ambiguity', desc: 'Using double meanings to mislead or misrepresent the truth.' }, |
| 34 | + { name: 'Genetic Fallacy', desc: 'Judging something based on where it comes from.' }, |
| 35 | + { name: 'Middle Ground', desc: 'Assuming a compromise between two extremes is the truth.' }, |
| 36 | + { name: 'Anecdotal', desc: 'Using personal experience instead of sound evidence.' } |
| 37 | +]; |
| 38 | + |
| 39 | +function LogicalFallaciesBingoPage() { |
| 40 | + const [grid, setGrid] = useState([]); |
| 41 | + const [marked, setMarked] = useState(new Set()); |
| 42 | + const [bingo, setBingo] = useState(false); |
| 43 | + |
| 44 | + const shuffleAndGenerate = () => { |
| 45 | + const shuffled = [...FALLACIES].sort(() => Math.random() - 0.5); |
| 46 | + const newGrid = []; |
| 47 | + let index = 0; |
| 48 | + for (let i = 0; i < 25; i++) { |
| 49 | + if (i === 12) { |
| 50 | + newGrid.push({ name: 'FREE SPACE', desc: 'You are using the internet', isFree: true }); |
| 51 | + } else { |
| 52 | + newGrid.push(shuffled[index]); |
| 53 | + index++; |
| 54 | + } |
| 55 | + } |
| 56 | + setGrid(newGrid); |
| 57 | + setMarked(new Set([12])); // Free space marked |
| 58 | + setBingo(false); |
| 59 | + }; |
| 60 | + |
| 61 | + useEffect(() => { |
| 62 | + shuffleAndGenerate(); |
| 63 | + }, []); |
| 64 | + |
| 65 | + const toggleMark = (index) => { |
| 66 | + if (index === 12) return; // Can't unmark free space |
| 67 | + const newMarked = new Set(marked); |
| 68 | + if (newMarked.has(index)) { |
| 69 | + newMarked.delete(index); |
| 70 | + } else { |
| 71 | + newMarked.add(index); |
| 72 | + } |
| 73 | + setMarked(newMarked); |
| 74 | + checkBingo(newMarked); |
| 75 | + }; |
| 76 | + |
| 77 | + const checkBingo = (currentMarked) => { |
| 78 | + const winningLines = [ |
| 79 | + [0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], [15, 16, 17, 18, 19], [20, 21, 22, 23, 24], // Rows |
| 80 | + [0, 5, 10, 15, 20], [1, 6, 11, 16, 21], [2, 7, 12, 17, 22], [3, 8, 13, 18, 23], [4, 9, 14, 19, 24], // Cols |
| 81 | + [0, 6, 12, 18, 24], [4, 8, 12, 16, 20] // Diagonals |
| 82 | + ]; |
| 83 | + |
| 84 | + const isBingo = winningLines.some(line => line.every(index => currentMarked.has(index))); |
| 85 | + setBingo(isBingo); |
| 86 | + }; |
| 87 | + |
| 88 | + return ( |
| 89 | + <div className="min-h-screen bg-[#050505] text-white selection:bg-emerald-500/30 font-sans"> |
| 90 | + <Seo |
| 91 | + title="Logical Fallacies Bingo | Fezcodex" |
| 92 | + description="A playable bingo game based on common logical fallacies found on the internet." |
| 93 | + keywords={['bingo', 'logical fallacies', 'game', 'logic', 'internet arguments']} |
| 94 | + /> |
| 95 | + <div className="mx-auto max-w-5xl px-6 py-24 md:px-12"> |
| 96 | + <header className="mb-12"> |
| 97 | + <Link |
| 98 | + to="/apps" |
| 99 | + className="group mb-12 inline-flex items-center gap-2 text-xs font-mono text-gray-500 hover:text-white transition-colors uppercase tracking-[0.3em]" |
| 100 | + > |
| 101 | + <ArrowLeftIcon |
| 102 | + weight="bold" |
| 103 | + className="transition-transform group-hover:-translate-x-1" |
| 104 | + /> |
| 105 | + <span>Applications</span> |
| 106 | + </Link> |
| 107 | + |
| 108 | + <div className="flex flex-col md:flex-row md:items-end justify-between gap-8"> |
| 109 | + <div className="space-y-4"> |
| 110 | + <BreadcrumbTitle |
| 111 | + title="Fallacy Bingo" |
| 112 | + slug="fb" |
| 113 | + variant="brutalist" |
| 114 | + /> |
| 115 | + <p className="text-xl text-gray-400 max-w-2xl font-light leading-relaxed"> |
| 116 | + Surviving a family dinner or a Twitter thread? Keep track of the terrible arguments you encounter with this interactive bingo board. Need a refresher? Read the <Link to="/posts/encyclopedia-of-bad-arguments" className="text-emerald-400 hover:text-emerald-300 underline decoration-emerald-500/30 underline-offset-4">Encyclopedia of Bad Arguments</Link>. |
| 117 | + </p> |
| 118 | + </div> |
| 119 | + |
| 120 | + <button |
| 121 | + onClick={shuffleAndGenerate} |
| 122 | + className="group relative inline-flex items-center gap-4 px-8 py-4 bg-white text-black hover:bg-emerald-400 transition-all duration-300 font-mono uppercase tracking-widest text-sm font-black rounded-sm shrink-0" |
| 123 | + > |
| 124 | + <ArrowsClockwiseIcon weight="bold" size={20} className={bingo ? "animate-spin" : ""} /> |
| 125 | + <span>Shuffle Board</span> |
| 126 | + </button> |
| 127 | + </div> |
| 128 | + </header> |
| 129 | + |
| 130 | + {bingo && ( |
| 131 | + <div className="mb-8 p-6 bg-emerald-500/20 border border-emerald-500/50 rounded-sm flex items-center gap-4 text-emerald-400 animate-pulse"> |
| 132 | + <CheckCircleIcon size={32} weight="fill" /> |
| 133 | + <div> |
| 134 | + <h2 className="text-xl font-bold uppercase tracking-widest font-mono">Bingo!</h2> |
| 135 | + <p className="text-sm opacity-80">You've successfully witnessed a complete collapse of logic.</p> |
| 136 | + </div> |
| 137 | + </div> |
| 138 | + )} |
| 139 | + |
| 140 | + <div className="grid grid-cols-5 gap-2 md:gap-4 w-full aspect-square max-w-4xl mx-auto"> |
| 141 | + {grid.map((cell, index) => { |
| 142 | + const isMarked = marked.has(index); |
| 143 | + const isFree = cell.isFree; |
| 144 | + return ( |
| 145 | + <button |
| 146 | + key={index} |
| 147 | + onClick={() => toggleMark(index)} |
| 148 | + className={` |
| 149 | + relative flex flex-col items-center justify-center p-2 md:p-4 text-center border transition-all duration-300 rounded-sm |
| 150 | + ${isMarked |
| 151 | + ? 'bg-emerald-500/20 border-emerald-500/50 text-emerald-400 shadow-[inset_0_0_20px_rgba(16,185,129,0.2)]' |
| 152 | + : 'bg-white/[0.02] border-white/10 hover:border-white/30 hover:bg-white/[0.05] text-gray-300' |
| 153 | + } |
| 154 | + ${isFree ? 'bg-white/5 border-white/20' : ''} |
| 155 | + `} |
| 156 | + > |
| 157 | + <span className="font-mono text-[9px] md:text-xs font-bold uppercase tracking-wider mb-1 md:mb-2 leading-tight"> |
| 158 | + {cell.name} |
| 159 | + </span> |
| 160 | + {!isFree && ( |
| 161 | + <span className="hidden md:block text-[10px] opacity-60 leading-tight"> |
| 162 | + {cell.desc} |
| 163 | + </span> |
| 164 | + )} |
| 165 | + {isMarked && !isFree && ( |
| 166 | + <div className="absolute top-2 right-2 text-emerald-500"> |
| 167 | + <CheckCircleIcon weight="fill" size={16} /> |
| 168 | + </div> |
| 169 | + )} |
| 170 | + {isFree && ( |
| 171 | + <BrainIcon className="mt-2 text-emerald-500/50" size={24} /> |
| 172 | + )} |
| 173 | + </button> |
| 174 | + ); |
| 175 | + })} |
| 176 | + </div> |
| 177 | + |
| 178 | + <footer className="mt-24 pt-12 border-t border-white/10 flex flex-col md:flex-row justify-between items-center gap-6 text-gray-600 font-mono text-[10px] uppercase tracking-[0.3em]"> |
| 179 | + <span>Fezcodex_Logic_Defense_Module_v1.0</span> |
| 180 | + <span className="text-gray-800">READY</span> |
| 181 | + </footer> |
| 182 | + </div> |
| 183 | + </div> |
| 184 | + ); |
| 185 | +} |
| 186 | + |
| 187 | +export default LogicalFallaciesBingoPage; |
0 commit comments