From e8183499c6be1f28d91e145170350e92c09bcfb1 Mon Sep 17 00:00:00 2001 From: Elijah R Date: Tue, 18 Jun 2024 21:57:56 -0400 Subject: [PATCH] add support for popular vote elections and add nk 2019 --- assets/NorthKorea2019.svg | 66 ++++++++++++++++++++++++++ src/VoteType.ts | 15 ++++++ src/election.ts | 6 ++- src/elections/1789.ts | 2 + src/elections/1848.ts | 2 + src/elections/1864.ts | 2 + src/elections/1940.ts | 2 + src/elections/1968.ts | 2 + src/elections/1992.ts | 2 + src/elections/2000.ts | 2 + src/elections/2004.ts | 2 + src/elections/2008.ts | 2 + src/elections/2024.ts | 2 + src/elections/NK2019.ts | 94 ++++++++++++++++++++++++++++++++++++++ src/elections/elections.ts | 6 ++- src/index.ts | 3 +- src/predictor.ts | 31 +++++++++++-- 17 files changed, 235 insertions(+), 6 deletions(-) create mode 100644 assets/NorthKorea2019.svg create mode 100644 src/VoteType.ts create mode 100644 src/elections/NK2019.ts diff --git a/assets/NorthKorea2019.svg b/assets/NorthKorea2019.svg new file mode 100644 index 0000000..a7f1f7b --- /dev/null +++ b/assets/NorthKorea2019.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/VoteType.ts b/src/VoteType.ts new file mode 100644 index 0000000..febf2dd --- /dev/null +++ b/src/VoteType.ts @@ -0,0 +1,15 @@ +enum VoteType { + Electoral, + Popular, +} + +export function VoteTypeString(voteType: VoteType) { + switch (voteType) { + case VoteType.Electoral: + return "Electoral Votes"; + case VoteType.Popular: + return "Votes"; + } +} + +export default VoteType; \ No newline at end of file diff --git a/src/election.ts b/src/election.ts index 9390056..62d7904 100644 --- a/src/election.ts +++ b/src/election.ts @@ -1,7 +1,10 @@ +import VoteType from './VoteType.js'; + export default interface Election { title : string, shortname : string, description : string, + voteType : VoteType, svg: string, candidates : { name : string, @@ -10,7 +13,8 @@ export default interface Election { }[], states : { [key : string] : { - electoralVotes : number, + electoralVotes? : number | undefined, + population? : number | undefined, odds : { [key : string] : number } diff --git a/src/elections/1789.ts b/src/elections/1789.ts index db969ed..caae7e1 100644 --- a/src/elections/1789.ts +++ b/src/elections/1789.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1789 = { title: "1789 United States Presidential Election Simulator", shortname: "1789", description: "The CalubViem Press has rewritten history and called the 1789 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1789.svg", "utf-8"), candidates: [ { diff --git a/src/elections/1848.ts b/src/elections/1848.ts index 3e4392e..e0c00f7 100644 --- a/src/elections/1848.ts +++ b/src/elections/1848.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1848 = { title: "1848 United States Presidential Election Simulator", shortname: "1848", description: "The CalubViem Press has rewritten history and called the 1848 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1848.svg", "utf-8"), candidates: [ { diff --git a/src/elections/1864.ts b/src/elections/1864.ts index f8eef90..534ada1 100644 --- a/src/elections/1864.ts +++ b/src/elections/1864.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1864 = { title: "1864 United States Presidential Election Simulator", shortname: "1864", description: "The CalubViem Press has rewritten history and called the 1864 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1864.svg", "utf-8"), candidates: [ { diff --git a/src/elections/1940.ts b/src/elections/1940.ts index c20b3a9..35aac3c 100644 --- a/src/elections/1940.ts +++ b/src/elections/1940.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1968 = { title: "1968 United States Presidential Election Simulator", shortname: "1968", description: "The CalubViem Press has rewritten history and called the 1968 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1968.svg", "utf-8"), candidates: [ { diff --git a/src/elections/1968.ts b/src/elections/1968.ts index c20b3a9..35aac3c 100644 --- a/src/elections/1968.ts +++ b/src/elections/1968.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1968 = { title: "1968 United States Presidential Election Simulator", shortname: "1968", description: "The CalubViem Press has rewritten history and called the 1968 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1968.svg", "utf-8"), candidates: [ { diff --git a/src/elections/1992.ts b/src/elections/1992.ts index db378a5..a150bd1 100644 --- a/src/elections/1992.ts +++ b/src/elections/1992.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_1992 = { title: "1992 United States Presidential Election Simulator", shortname: "1992", description: "The CalubViem Press has rewritten history and called the 1992 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1992.svg", "utf-8"), candidates: [ { diff --git a/src/elections/2000.ts b/src/elections/2000.ts index e08cd87..4067490 100644 --- a/src/elections/2000.ts +++ b/src/elections/2000.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_2000 = { title: "2000 United States Presidential Election Simulator", shortname: "2000", description: "The CalubViem Press has rewritten history and called the 2000 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege1992.svg", "utf-8"), candidates: [ { diff --git a/src/elections/2004.ts b/src/elections/2004.ts index a4c2b6a..f508811 100644 --- a/src/elections/2004.ts +++ b/src/elections/2004.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_2004 = { title: "2004 United States Presidential Election Simulator", shortname: "2004", description: "The CalubViem Press has rewritten history and called the 2004 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege2004.svg", "utf-8"), candidates: [ { diff --git a/src/elections/2008.ts b/src/elections/2008.ts index 07bda7e..8d61aeb 100644 --- a/src/elections/2008.ts +++ b/src/elections/2008.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_2008 = { title: "2008 United States Presidential Election Simulator", shortname: "2008", description: "The CalubViem Press has rewritten history and called the 2008 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege2008.svg", "utf-8"), candidates: [ { diff --git a/src/elections/2024.ts b/src/elections/2024.ts index c8d6c4f..dab8a21 100644 --- a/src/elections/2024.ts +++ b/src/elections/2024.ts @@ -1,10 +1,12 @@ import { readFile } from "node:fs/promises"; import Election from "../election.js"; +import VoteType from "../VoteType.js"; const Presidential_2024 = { title: "2024 United States Presidential Election Simulator", shortname: "2024", description: "The CalubViem Press has called the 2024 United States Presidential Election for $WINNER!", + voteType: VoteType.Electoral, svg: await readFile("assets/ElectoralCollege2024.svg", "utf-8"), candidates: [ { diff --git a/src/elections/NK2019.ts b/src/elections/NK2019.ts new file mode 100644 index 0000000..4965a9a --- /dev/null +++ b/src/elections/NK2019.ts @@ -0,0 +1,94 @@ +import { readFile } from "fs/promises"; +import VoteType from "../VoteType.js"; +import Election from "../election.js" + +const NK_2019 = { + title: "2019 North Korean Supreme People's Assembly Election", + shortname: "nk2019", + description: "The Central Election Committee of the Democratic People's Republic of Korea has called the election for $WINNER!", + voteType: VoteType.Popular, + svg: await readFile("assets/NorthKorea2019.svg", "utf-8"), + candidates: [ + { + name: "Kim Jong-Un", + party: "Democratic Front FTRF", + color: "#FF0000", + }, + ], + states: { + "KP-01": { + population: 3157538, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-02": { + population: 4051696, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-03": { + population: 12191, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-04": { + population: 1299830, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-05": { + population: 8450, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-06": { + population: 8154, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-07": { + population: 11255, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-08": { + population: 18970, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-09": { + population: 2327362, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-10": { + population: 719269, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-13": { + population: 205000, + odds: { + "Democratic Front FTRF": 1, + } + }, + "KP-14": { + population: 983660, + odds: { + "Democratic Front FTRF": 1, + } + }, + } +} + +export default NK_2019 as Election; \ No newline at end of file diff --git a/src/elections/elections.ts b/src/elections/elections.ts index 8087308..7521869 100644 --- a/src/elections/elections.ts +++ b/src/elections/elections.ts @@ -8,8 +8,10 @@ import Presidential_2000 from "./2000.js"; import Presidential_2004 from "./2004.js"; import Presidential_2008 from "./2008.js"; import Presidential_2024 from "./2024.js"; +import NK_2019 from "./NK2019.js"; const Elections = { + // US "1789": Presidential_1789, "1848": Presidential_1848, "1864": Presidential_1864, @@ -18,7 +20,9 @@ const Elections = { "2000": Presidential_2000, "2004": Presidential_2004, "2008": Presidential_2008, - "2024": Presidential_2024 + "2024": Presidential_2024, + // Other + "nk2019": NK_2019, } export default (Elections as {[key : string] : Election}) \ No newline at end of file diff --git a/src/index.ts b/src/index.ts index 1d67d13..81ec061 100644 --- a/src/index.ts +++ b/src/index.ts @@ -5,6 +5,7 @@ import {MakePrediction} from "./predictor.js"; import Election from "./election.js"; import Presidential_2024 from "./elections/2024.js"; import Elections from "./elections/elections.js"; +import VoteType, { VoteTypeString } from "./VoteType.js"; const configraw = fs.readFileSync("config.json", "utf-8"); const config = JSON.parse(configraw); @@ -100,7 +101,7 @@ if (!config.token) { .addFields(result.candidates.map(c => { return { name: `${(result.winner === c.name ? ":white_check_mark:" : "")} ${c.name} (${c.party})`, - value: `${c.votes} Electoral Votes`, + value: `${c.votes} ${VoteTypeString(election.voteType)}${election.voteType === VoteType.Popular ? ` (${((c.votes / result.totalVotes) * 100).toFixed(2)}%)` : ""}`, inline: true } })) diff --git a/src/predictor.ts b/src/predictor.ts index 7c712dc..cebcce5 100644 --- a/src/predictor.ts +++ b/src/predictor.ts @@ -3,6 +3,7 @@ import { SVG, registerWindow } from '@svgdotjs/svg.js' import sharp from "sharp"; import crypto from "crypto"; import Election from "./election.js"; +import VoteType from './VoteType.js'; export function MakePrediction(election : Election) : Promise { return new Promise(async res => { @@ -24,8 +25,17 @@ export function MakePrediction(election : Election) : Promise { if (Object.keys(election.states[state].odds).every(p => election.states[state].odds[p] <= 0)) for (const candidate of election.candidates) election.states[state].odds[candidate.party] = 1; - var winner = await weightedRand(election.states[state].odds); - pred.candidates.find((c : any) => c.party === winner).votes += election.states[state].electoralVotes; + var winner = ""; + if (election.voteType === VoteType.Electoral) { + winner = await weightedRand(election.states[state].odds); + pred.candidates.find((c : any) => c.party === winner).votes += election.states[state].electoralVotes; + } else if (election.voteType === VoteType.Popular) { + var votes = await getVotes(election.states[state].population!, election.states[state].odds); + winner = Object.keys(votes).sort((a, b) => votes[b] - votes[a])[0]; + for (const candidate of election.candidates) { + pred.candidates.find((c : any) => c.party === candidate.party).votes += votes[candidate.party]; + } + } // @ts-ignore draw.find(`#${state}`).fill(election.candidates.find((c : any) => c.party === winner).color); } @@ -33,6 +43,7 @@ export function MakePrediction(election : Election) : Promise { var s = sharp(Buffer.from(draw.svg())); var png = await s.png().toBuffer(); pred.winner = pred.candidates.sort((a : any, b : any) => b.votes - a.votes)[0].name; + pred.totalVotes = pred.candidates.reduce((a : any, b : any) => a + b.votes, 0); pred.svg = draw.svg(); pred.png = png; res(pred); @@ -44,9 +55,10 @@ export interface Prediction { candidates : { name : string, party : string, - votes : string, + votes : number, }[], winner: string, + totalVotes: number, svg: string, png: Buffer, } @@ -71,4 +83,17 @@ export async function weightedRand(spec : { [key : string] : number }) : Promise } } return table[await betterRandom(0, table.length)]; +} + +export async function getVotes(population: number, spec: { [key: string]: number }) : Promise<{ [key: string]: number }>{ + // split the population into 100 chunks + var chunk = population / 100; + var votes = {} as { [key: string]: number }; + for (var c in spec) votes[c] = 0; + for (var i = 0; i < 100; i++) { + var winner = await weightedRand(spec); + votes[winner] += chunk; + } + for (var c in votes) votes[c] = Math.round(votes[c]); + return votes; } \ No newline at end of file