Advertisement
Guest User

Eurovision Sweden Management Game (tsx)

a guest
May 29th, 2025
6
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 17.67 KB | None | 0 0
  1. import React, { useState, useEffect } from 'react';
  2. import { Music, Star, Users, Trophy, Timer, DollarSign, Globe } from 'lucide-react';
  3.  
  4. const EurovisionSimulator = () => {
  5. const [gameState, setGameState] = useState({
  6. phase: 'selection', // selection, preparation, competition
  7. budget: 1000000, // 1 million SEK
  8. publicSupport: 50,
  9. jurySupport: 50,
  10. internationalAppeal: 30,
  11. mediaAttention: 20,
  12. week: 1,
  13. maxWeeks: 12,
  14. finalScore: 0,
  15. placement: 0
  16. });
  17.  
  18. const [selections, setSelections] = useState({
  19. artist: null,
  20. song: null,
  21. staging: null,
  22. choreographer: null,
  23. stylist: null
  24. });
  25.  
  26. const [events, setEvents] = useState([]);
  27.  
  28. const artists = [
  29. {
  30. id: 'pop_star',
  31. name: 'Astrid Lindberg',
  32. type: 'Pop Star',
  33. cost: 300000,
  34. publicAppeal: 80,
  35. juryAppeal: 60,
  36. international: 70,
  37. description: 'Famous Swedish pop sensation with chart-topping hits'
  38. },
  39. {
  40. id: 'indie_artist',
  41. name: 'Erik Svensson',
  42. type: 'Indie Artist',
  43. cost: 150000,
  44. publicAppeal: 60,
  45. juryAppeal: 85,
  46. international: 50,
  47. description: 'Critically acclaimed indie musician with unique sound'
  48. },
  49. {
  50. id: 'newcomer',
  51. name: 'Saga Nilsson',
  52. type: 'Newcomer',
  53. cost: 50000,
  54. publicAppeal: 40,
  55. juryAppeal: 70,
  56. international: 60,
  57. description: 'Fresh talent with viral social media presence'
  58. }
  59. ];
  60.  
  61. const songs = [
  62. {
  63. id: 'upbeat_anthem',
  64. name: 'Northern Lights',
  65. type: 'Upbeat Anthem',
  66. cost: 200000,
  67. publicAppeal: 75,
  68. juryAppeal: 65,
  69. international: 80,
  70. description: 'High-energy anthem about Swedish nature and freedom'
  71. },
  72. {
  73. id: 'ballad',
  74. name: 'Silent Snow',
  75. type: 'Emotional Ballad',
  76. cost: 150000,
  77. publicAppeal: 60,
  78. juryAppeal: 90,
  79. international: 70,
  80. description: 'Haunting ballad showcasing vocal prowess'
  81. },
  82. {
  83. id: 'experimental',
  84. name: 'Digital Dreams',
  85. type: 'Electronic Fusion',
  86. cost: 250000,
  87. publicAppeal: 50,
  88. juryAppeal: 75,
  89. international: 85,
  90. description: 'Genre-bending electronic track with Swedish folk elements'
  91. }
  92. ];
  93.  
  94. const stagingOptions = [
  95. {
  96. id: 'minimalist',
  97. name: 'Minimalist Nordic',
  98. cost: 200000,
  99. effect: { publicSupport: 5, jurySupport: 15, international: 10 }
  100. },
  101. {
  102. id: 'spectacular',
  103. name: 'Spectacular Show',
  104. cost: 400000,
  105. effect: { publicSupport: 20, jurySupport: 5, international: 15 }
  106. },
  107. {
  108. id: 'cultural',
  109. name: 'Swedish Heritage',
  110. cost: 300000,
  111. effect: { publicSupport: 15, jurySupport: 10, international: 5 }
  112. }
  113. ];
  114.  
  115. const makeSelection = (category, option) => {
  116. if (gameState.budget >= option.cost) {
  117. setGameState(prev => ({
  118. ...prev,
  119. budget: prev.budget - option.cost,
  120. publicSupport: Math.min(100, prev.publicSupport + (option.publicAppeal || 0) / 5),
  121. jurySupport: Math.min(100, prev.jurySupport + (option.juryAppeal || 0) / 5),
  122. internationalAppeal: Math.min(100, prev.internationalAppeal + (option.international || 0) / 5)
  123. }));
  124.  
  125. setSelections(prev => ({
  126. ...prev,
  127. [category]: option
  128. }));
  129.  
  130. addEvent(`Selected ${option.name} for ${option.cost.toLocaleString()} SEK`);
  131. }
  132. };
  133.  
  134. const addEvent = (message) => {
  135. setEvents(prev => [...prev, { week: gameState.week, message }]);
  136. };
  137.  
  138. const advanceWeek = () => {
  139. if (gameState.week >= gameState.maxWeeks) {
  140. calculateFinalResult();
  141. return;
  142. }
  143.  
  144. const randomEvents = [
  145. {
  146. message: "Positive media coverage boosts international attention",
  147. effect: { internationalAppeal: 5, mediaAttention: 10 }
  148. },
  149. {
  150. message: "Social media buzz increases public support",
  151. effect: { publicSupport: 8, mediaAttention: 5 }
  152. },
  153. {
  154. message: "Music critics praise the artistic vision",
  155. effect: { jurySupport: 10 }
  156. },
  157. {
  158. message: "Technical rehearsal goes smoothly",
  159. effect: { jurySupport: 5, publicSupport: 3 }
  160. }
  161. ];
  162.  
  163. if (Math.random() < 0.3) {
  164. const event = randomEvents[Math.floor(Math.random() * randomEvents.length)];
  165. addEvent(event.message);
  166.  
  167. setGameState(prev => ({
  168. ...prev,
  169. week: prev.week + 1,
  170. publicSupport: Math.min(100, prev.publicSupport + (event.effect.publicSupport || 0)),
  171. jurySupport: Math.min(100, prev.jurySupport + (event.effect.jurySupport || 0)),
  172. internationalAppeal: Math.min(100, prev.internationalAppeal + (event.effect.internationalAppeal || 0)),
  173. mediaAttention: Math.min(100, prev.mediaAttention + (event.effect.mediaAttention || 0))
  174. }));
  175. } else {
  176. setGameState(prev => ({ ...prev, week: prev.week + 1 }));
  177. }
  178. };
  179.  
  180. const calculateFinalResult = () => {
  181. const { publicSupport, jurySupport, internationalAppeal, mediaAttention } = gameState;
  182. const score = (publicSupport * 0.3 + jurySupport * 0.4 + internationalAppeal * 0.2 + mediaAttention * 0.1);
  183.  
  184. let placement;
  185. if (score >= 85) placement = Math.floor(Math.random() * 3) + 1; // Top 3
  186. else if (score >= 70) placement = Math.floor(Math.random() * 7) + 4; // 4-10
  187. else if (score >= 50) placement = Math.floor(Math.random() * 10) + 11; // 11-20
  188. else placement = Math.floor(Math.random() * 6) + 21; // 21-26
  189.  
  190. setGameState(prev => ({
  191. ...prev,
  192. phase: 'results',
  193. finalScore: Math.round(score),
  194. placement
  195. }));
  196. };
  197.  
  198. const startPreparation = () => {
  199. if (selections.artist && selections.song) {
  200. setGameState(prev => ({ ...prev, phase: 'preparation' }));
  201. addEvent("Preparation phase begins! Time to fine-tune your Eurovision entry.");
  202. }
  203. };
  204.  
  205. const resetGame = () => {
  206. setGameState({
  207. phase: 'selection',
  208. budget: 1000000,
  209. publicSupport: 50,
  210. jurySupport: 50,
  211. internationalAppeal: 30,
  212. mediaAttention: 20,
  213. week: 1,
  214. maxWeeks: 12,
  215. finalScore: 0,
  216. placement: 0
  217. });
  218. setSelections({
  219. artist: null,
  220. song: null,
  221. staging: null,
  222. choreographer: null,
  223. stylist: null
  224. });
  225. setEvents([]);
  226. };
  227.  
  228. if (gameState.phase === 'results') {
  229. return (
  230. <div className="max-w-4xl mx-auto p-6 bg-gradient-to-br from-blue-50 to-yellow-50 min-h-screen">
  231. <div className="bg-white rounded-lg shadow-xl p-8 text-center">
  232. <Trophy className="w-16 h-16 mx-auto mb-4 text-yellow-500" />
  233. <h1 className="text-3xl font-bold mb-4">Eurovision 2025 Results</h1>
  234.  
  235. <div className="bg-gradient-to-r from-blue-500 to-yellow-500 text-white p-6 rounded-lg mb-6">
  236. <h2 className="text-2xl font-bold mb-2">Sweden finished #{gameState.placement}</h2>
  237. <p className="text-lg">Final Score: {gameState.finalScore}/100</p>
  238. </div>
  239.  
  240. <div className="grid grid-cols-2 md:grid-cols-4 gap-4 mb-6">
  241. <div className="bg-blue-100 p-4 rounded">
  242. <Users className="w-8 h-8 mx-auto mb-2 text-blue-600" />
  243. <p className="font-semibold">Public</p>
  244. <p>{Math.round(gameState.publicSupport)}%</p>
  245. </div>
  246. <div className="bg-green-100 p-4 rounded">
  247. <Star className="w-8 h-8 mx-auto mb-2 text-green-600" />
  248. <p className="font-semibold">Jury</p>
  249. <p>{Math.round(gameState.jurySupport)}%</p>
  250. </div>
  251. <div className="bg-purple-100 p-4 rounded">
  252. <Globe className="w-8 h-8 mx-auto mb-2 text-purple-600" />
  253. <p className="font-semibold">International</p>
  254. <p>{Math.round(gameState.internationalAppeal)}%</p>
  255. </div>
  256. <div className="bg-orange-100 p-4 rounded">
  257. <div className="w-8 h-8 mx-auto mb-2 bg-orange-600 rounded"></div>
  258. <p className="font-semibold">Media</p>
  259. <p>{Math.round(gameState.mediaAttention)}%</p>
  260. </div>
  261. </div>
  262.  
  263. <div className="mb-6">
  264. <h3 className="text-xl font-bold mb-2">Your Entry</h3>
  265. <p><strong>Artist:</strong> {selections.artist?.name}</p>
  266. <p><strong>Song:</strong> {selections.song?.name}</p>
  267. <p><strong>Staging:</strong> {selections.staging?.name || 'Basic staging'}</p>
  268. </div>
  269.  
  270. <button
  271. onClick={resetGame}
  272. className="bg-blue-600 text-white px-6 py-3 rounded-lg hover:bg-blue-700 transition-colors"
  273. >
  274. Manage Sweden Again
  275. </button>
  276. </div>
  277. </div>
  278. );
  279. }
  280.  
  281. return (
  282. <div className="max-w-6xl mx-auto p-6 bg-gradient-to-br from-blue-50 to-yellow-50 min-h-screen">
  283. <div className="bg-white rounded-lg shadow-xl p-6">
  284. <div className="flex items-center justify-between mb-6">
  285. <div className="flex items-center gap-3">
  286. <Music className="w-8 h-8 text-blue-600" />
  287. <h1 className="text-3xl font-bold text-gray-800">Eurovision Sweden Manager</h1>
  288. </div>
  289. <div className="text-right">
  290. <p className="text-lg font-semibold text-blue-600">🇸🇪 Sweden 2025</p>
  291. </div>
  292. </div>
  293.  
  294. {/* Status Bar */}
  295. <div className="grid grid-cols-2 md:grid-cols-5 gap-4 mb-6 bg-gray-50 p-4 rounded-lg">
  296. <div className="flex items-center gap-2">
  297. <DollarSign className="w-5 h-5 text-green-600" />
  298. <div>
  299. <p className="text-sm text-gray-600">Budget</p>
  300. <p className="font-semibold">{gameState.budget.toLocaleString()} SEK</p>
  301. </div>
  302. </div>
  303. <div className="flex items-center gap-2">
  304. <Users className="w-5 h-5 text-blue-600" />
  305. <div>
  306. <p className="text-sm text-gray-600">Public Support</p>
  307. <p className="font-semibold">{Math.round(gameState.publicSupport)}%</p>
  308. </div>
  309. </div>
  310. <div className="flex items-center gap-2">
  311. <Star className="w-5 h-5 text-yellow-600" />
  312. <div>
  313. <p className="text-sm text-gray-600">Jury Appeal</p>
  314. <p className="font-semibold">{Math.round(gameState.jurySupport)}%</p>
  315. </div>
  316. </div>
  317. <div className="flex items-center gap-2">
  318. <Globe className="w-5 h-5 text-purple-600" />
  319. <div>
  320. <p className="text-sm text-gray-600">International</p>
  321. <p className="font-semibold">{Math.round(gameState.internationalAppeal)}%</p>
  322. </div>
  323. </div>
  324. <div className="flex items-center gap-2">
  325. <Timer className="w-5 h-5 text-red-600" />
  326. <div>
  327. <p className="text-sm text-gray-600">Week</p>
  328. <p className="font-semibold">{gameState.week}/{gameState.maxWeeks}</p>
  329. </div>
  330. </div>
  331. </div>
  332.  
  333. {gameState.phase === 'selection' && (
  334. <div className="space-y-8">
  335. <div>
  336. <h2 className="text-2xl font-bold mb-4">Choose Your Artist</h2>
  337. <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
  338. {artists.map(artist => (
  339. <div
  340. key={artist.id}
  341. className={`border-2 rounded-lg p-4 cursor-pointer transition-all ${
  342. selections.artist?.id === artist.id
  343. ? 'border-blue-500 bg-blue-50'
  344. : 'border-gray-200 hover:border-gray-300'
  345. }`}
  346. onClick={() => makeSelection('artist', artist)}
  347. >
  348. <h3 className="font-bold text-lg">{artist.name}</h3>
  349. <p className="text-gray-600 mb-2">{artist.type}</p>
  350. <p className="text-sm mb-3">{artist.description}</p>
  351. <div className="space-y-1 text-sm">
  352. <p>Cost: {artist.cost.toLocaleString()} SEK</p>
  353. <p>Public Appeal: {artist.publicAppeal}%</p>
  354. <p>Jury Appeal: {artist.juryAppeal}%</p>
  355. <p>International: {artist.international}%</p>
  356. </div>
  357. </div>
  358. ))}
  359. </div>
  360. </div>
  361.  
  362. <div>
  363. <h2 className="text-2xl font-bold mb-4">Choose Your Song</h2>
  364. <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
  365. {songs.map(song => (
  366. <div
  367. key={song.id}
  368. className={`border-2 rounded-lg p-4 cursor-pointer transition-all ${
  369. selections.song?.id === song.id
  370. ? 'border-blue-500 bg-blue-50'
  371. : 'border-gray-200 hover:border-gray-300'
  372. }`}
  373. onClick={() => makeSelection('song', song)}
  374. >
  375. <h3 className="font-bold text-lg">{song.name}</h3>
  376. <p className="text-gray-600 mb-2">{song.type}</p>
  377. <p className="text-sm mb-3">{song.description}</p>
  378. <div className="space-y-1 text-sm">
  379. <p>Cost: {song.cost.toLocaleString()} SEK</p>
  380. <p>Public Appeal: {song.publicAppeal}%</p>
  381. <p>Jury Appeal: {song.juryAppeal}%</p>
  382. <p>International: {song.international}%</p>
  383. </div>
  384. </div>
  385. ))}
  386. </div>
  387. </div>
  388.  
  389. {selections.artist && selections.song && (
  390. <div className="text-center">
  391. <button
  392. onClick={startPreparation}
  393. className="bg-blue-600 text-white px-8 py-3 rounded-lg text-lg font-semibold hover:bg-blue-700 transition-colors"
  394. >
  395. Start Preparation Phase
  396. </button>
  397. </div>
  398. )}
  399. </div>
  400. )}
  401.  
  402. {gameState.phase === 'preparation' && (
  403. <div className="space-y-6">
  404. <div className="bg-blue-50 p-4 rounded-lg">
  405. <h2 className="text-2xl font-bold mb-2">Preparation Phase</h2>
  406. <p className="text-gray-700">Fine-tune your entry with staging, choreography, and styling choices.</p>
  407. </div>
  408.  
  409. <div>
  410. <h3 className="text-xl font-bold mb-4">Staging Options</h3>
  411. <div className="grid grid-cols-1 md:grid-cols-3 gap-4">
  412. {stagingOptions.map(staging => (
  413. <div
  414. key={staging.id}
  415. className={`border-2 rounded-lg p-4 cursor-pointer transition-all ${
  416. selections.staging?.id === staging.id
  417. ? 'border-blue-500 bg-blue-50'
  418. : 'border-gray-200 hover:border-gray-300'
  419. }`}
  420. onClick={() => {
  421. if (gameState.budget >= staging.cost) {
  422. setGameState(prev => ({
  423. ...prev,
  424. budget: prev.budget - staging.cost,
  425. ...Object.keys(staging.effect).reduce((acc, key) => {
  426. acc[key] = Math.min(100, prev[key] + staging.effect[key]);
  427. return acc;
  428. }, {})
  429. }));
  430. setSelections(prev => ({ ...prev, staging }));
  431. }
  432. }}
  433. >
  434. <h4 className="font-bold">{staging.name}</h4>
  435. <p className="text-sm text-gray-600 mb-2">Cost: {staging.cost.toLocaleString()} SEK</p>
  436. <div className="text-sm">
  437. {Object.entries(staging.effect).map(([key, value]) => (
  438. <p key={key}>+{value} {key.replace(/([A-Z])/g, ' $1').toLowerCase()}</p>
  439. ))}
  440. </div>
  441. </div>
  442. ))}
  443. </div>
  444. </div>
  445.  
  446. <div className="flex justify-between items-center">
  447. <button
  448. onClick={advanceWeek}
  449. className="bg-green-600 text-white px-6 py-2 rounded-lg hover:bg-green-700 transition-colors"
  450. >
  451. Advance Week
  452. </button>
  453.  
  454. {gameState.week >= gameState.maxWeeks && (
  455. <button
  456. onClick={calculateFinalResult}
  457. className="bg-yellow-600 text-white px-6 py-2 rounded-lg hover:bg-yellow-700 transition-colors"
  458. >
  459. Go to Eurovision!
  460. </button>
  461. )}
  462. </div>
  463.  
  464. {events.length > 0 && (
  465. <div className="bg-gray-50 p-4 rounded-lg">
  466. <h3 className="font-bold mb-2">Recent Events</h3>
  467. <div className="space-y-1 text-sm max-h-32 overflow-y-auto">
  468. {events.slice(-5).map((event, idx) => (
  469. <p key={idx}><strong>Week {event.week}:</strong> {event.message}</p>
  470. ))}
  471. </div>
  472. </div>
  473. )}
  474. </div>
  475. )}
  476. </div>
  477. </div>
  478. );
  479. };
  480.  
  481. export default EurovisionSimulator;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement