diff --git a/src/app/mods/backgammon.ts b/src/app/mods/backgammon.ts index 82c6406e8..68f8e8abb 100644 --- a/src/app/mods/backgammon.ts +++ b/src/app/mods/backgammon.ts @@ -22,6 +22,9 @@ export const backgammonPlugin: Plugin = { actions: [ { event: 'flip', label: $localize`flip` }, ], + advancedActions: [ + { tag: 'plugin/delta/backgammon', labelOff: $localize`play ai`, labelOn: $localize`cancel ai`, title: $localize`Play against an AI opponent.` }, + ], // language=CSS css: ` body.dark-theme { @@ -58,6 +61,128 @@ export const backgammonPlugin: Plugin = { } }; +export const backgammonAiPlugin: Plugin = { + tag: 'plugin/delta/backgammon', + name: $localize`🎲️👻️ AI Backgammon`, + config: { + mod: $localize`🎲️ Backgammon`, + type: 'plugin', + generated: $localize`Generated by jasper-ui ${moment().toISOString()}`, + description: $localize`Play backgammon against the AI.`, + icons: [{ label: $localize`👻️`, order: -3 }], + timeoutMs: 30_000, + language: 'javascript', + // language=JavaScript + script: ` + const axios = require('axios'); + const OpenAi = require('openai'); + const ref = JSON.parse(require('fs').readFileSync(0, 'utf-8')); + const roll = () => Math.floor(Math.random() * 6) + 1; + const origin = ref.origin || '' + const config = ref.plugins['plugin/delta/backgammon']; + if (!(ref.comment || '').trim()) process.exit(0); // Let other player roll first + const moves = ref.comment.split('\\n').map(m => m.trim()).filter(m => !!m); + let aiColor = (config?.aiColor.toLowerCase() === 'black' ? 'b' : 'r'); + let userRoll = 0; + let aiRoll = 0; + const parseRoll = m => { + if (m.startsWith(aiColor)) { + aiRoll = parseInt(m.substring(2, 3)); + } else { + userRoll = parseInt(m.substring(2, 3)); + } + } + let move = ''; + const lastMove = moves[moves.length - 1]; + if (lastMove.endsWith('-0')) { + for (let i = 0; i < moves.length; i += 2) { + userRoll = 0; + aiRoll = 0; + parseRoll(moves[i]); + if (i + 1 < moves.length) { + parseRoll(moves[i+1]); + } + } + if (!aiRoll) { + aiRoll = roll(); + move = aiColor + ' ' + aiRoll + '-0'; + } else if (userRoll) { + if (aiRoll <= userRoll) process.exit(0); // Let other player re-roll first + // AI won the roll, ask for a move + } + } + if (!move) { + const apiKey = (await axios.get(process.env.JASPER_API + '/api/v1/ref/page', { + headers: { + 'Local-Origin': origin || 'default', + 'User-Role': 'ROLE_ADMIN', + }, + params: { query: (config?.apiKeyTag || '+plugin/secret/openai') + origin }, + })).data.content[0].comment; + const prompt = + 'You are playing a game of backgammon against an opponent.\\n' + + 'You are the ' + config?.aiColor + ' player indicated by the ' + aiColor + ' character.\\n\\n' + + 'Read the following board and reply with the next move.\\n' + + 'Here is an example board:' + + 'r 2-0\\n' + + 'b 5-0\\n' + + 'b 4-4\\n' + + 'b 6/2(4)\\n' + + 'r 2-6\\n' + + 'r 12/18\\n' + + 'r 17/19\\n' + + 'Rolling will be done for you, if it is your turn to roll simply reply roll.\\n' + + 'If the game has ended or is not valid simply reply pass.\\n' + + 'Do not include any lists, formatting, additional text. Only reply with either a valid move or the word pass or roll in all lowercase.\\n\\n' + + ref.comment; + const openai = new OpenAi({ apiKey }); + const completion = await openai.chat.completions.create({ + model: config?.model || 'gpt-4o', + max_tokens: config?.maxTokens || 4096, + messages: [ + { role: 'system', content: prompt }, + { role: 'user', content: ref.comment }, + ], + }); + move = completion.choices[0]?.message?.content; + } + if (move !== 'pass') { + if (move === 'roll') { + move = aiColor + ' ' + roll() + '-' + roll(); + } + delete ref.metadata; + const newBoard = { + ...ref, + comment: (ref.comment ? ref.comment + '\\n' : '') + move, + }; + console.log(JSON.stringify({ + ref: [newBoard], + })); + } + `, + form: [{ + key: 'aiColor', + type: 'select', + defaultValue: 'Black', + props: { + label: $localize`AI Color:`, + options: [ + { value: 'Black', label: $localize`Black` }, + { value: 'Red', label: $localize`Red` }, + ], + } + }], + }, + defaults: { + aiColor: 'Black', + }, + schema: { + properties: { + aiColor: { type: 'string' }, + }, + }, +}; + export const backgammonTemplate: Template = { tag: 'plugin/backgammon', name: $localize`🎲️ Backgammon`, @@ -123,6 +248,7 @@ export const backgammonTemplate: Template = { export const backgammonMod: Mod = { plugins: { backgammonPlugin, + backgammonAiPlugin, }, templates: { backgammonTemplate,