This commit is contained in:
Simon Vieille 2022-01-31 13:50:48 +01:00
parent be1dd65ebb
commit b04f4d3c2d
9 changed files with 332587 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/node_modules
/datas
/test

15
README.md Normal file
View File

@ -0,0 +1,15 @@
# Matrix Motus Bot
Bot Matrix qui permet de jouer à Motus en message privé et sur un salon de discussion.
Il faut créer un compte destiné au robot puis :
```
git clone https://gitnet.fr/deblan/matrix-motus-bot.git
npm install
export MATRIX_URL='https://matrix.org'
export MATRIX_USERNAME='@username:matrix.org'
export MATRIX_PASSWORD='pa$wOrd'
export MATRIX_DATA='./datas' # non obligatoire
node index.js
```

149440
assets/foo Normal file

File diff suppressed because it is too large Load Diff

149444
assets/words.base.js Normal file

File diff suppressed because it is too large Load Diff

29846
assets/words.js Normal file

File diff suppressed because it is too large Load Diff

90
game.js Normal file
View File

@ -0,0 +1,90 @@
class Game {
constructor(expectedWord, words) {
this.expectedWord = expectedWord.toUpperCase()
this.triedWord = null
this.triedWords = []
this.maxTries = 6
this.words = words
this.won = false
}
tryWord(word) {
word = word.toUpperCase()
if (!this.acceptableWord(word)) {
return false
}
if (!this.isFinish()) {
this.triedWord = word
this.triedWords.push(this.triedWord)
this.won = this.triedWord === this.expectedWord
return true
}
return false
}
acceptableWord(word) {
if (word.length !== this.expectedWord.length) {
return false
}
return this.words.indexOf(word) > -1;
}
isFinish() {
return this.triedWords.length === this.maxTries || this.isWon()
}
isWon() {
return this.won
}
matrix() {
let results = []
for (let u = 0; u < this.maxTries; u++) {
let result = []
let word
if (this.triedWords.hasOwnProperty(u)) {
word = this.triedWords[u]
for (var x = 0; x < this.expectedWord.length; x++) {
let expectedLetter = this.expectedWord.charAt(x)
let triedLetter = this.triedWords[u].charAt(x)
if (expectedLetter === triedLetter) {
result.push(3)
} else {
if (this.expectedWord.indexOf(triedLetter) > -1) {
const numberOfTriedLetterInExpectedWord = this.expectedWord.split(triedLetter).length - 1
const cuttedTriedWord = this.triedWords[u].substr(0, x + 1)
const numberOfTriedLetterInCuttedWord = cuttedTriedWord.split(triedLetter).length - 1
if (numberOfTriedLetterInCuttedWord > numberOfTriedLetterInExpectedWord) {
result.push(1)
} else {
result.push(2)
}
} else {
result.push(1)
}
}
}
} else {
for (var x = 0; x < this.expectedWord.length; x++) {
result.push(0)
}
}
results.push({word, result})
}
return results
}
}
export default Game

187
index.js Normal file
View File

@ -0,0 +1,187 @@
import path from 'path'
import Game from './game.js'
import emoji from 'node-emoji'
import logTimestamp from 'log-timestamp'
import commander from 'bot-commander'
import showdown from 'showdown'
import uniqueRandomArray from 'unique-random-array'
import words from './assets/words.js'
import {
fileURLToPath
} from 'url'
import {
MatrixClient,
MatrixAuth,
SimpleFsStorageProvider,
RustSdkCryptoStorageProvider,
AutojoinRoomsMixin
} from 'matrix-bot-sdk'
const converter = new showdown.Converter()
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
const datasPath = process.env.MATRIX_DATA ?? (__dirname + '/datas')
const storage = new SimpleFsStorageProvider(datasPath + '/bot.json')
const cryptoProvider = new RustSdkCryptoStorageProvider(datasPath + '/crypto')
const url = process.env.MATRIX_URL
const username = process.env.MATRIX_USERNAME
const password = process.env.MATRIX_PASSWORD
const auth = new MatrixAuth(url)
const authClient = await auth.passwordLogin(username, password)
const token = authClient.accessToken
const client = new MatrixClient(url, token, storage, cryptoProvider)
const games = []
const randomWord = uniqueRandomArray(words);
const sendMessage = (room, text) => {
client.sendMessage(room, {
'msgtype': 'm.text',
'format': 'org.matrix.custom.html',
'body': text,
'formatted_body': converter.makeHtml(text),
});
}
const renderMatrix = (game) => {
const chars = {
0: ':large_blue_square:',
1: ':large_blue_square:',
2: ':large_yellow_circle:',
3: ':large_red_square:',
}
let render = '<table>'
for (let row of game.matrix()) {
const letters = (row.word ?? '.'.repeat(game.expectedWord.length)).split('')
let signs = []
for (let index in row.result) {
signs.push(chars[row.result[index]])
}
for (let element of [letters, signs]) {
render += '<tr>'
for (let item of element) {
render += `<td>${item}</td>`
}
render += '</tr>'
}
}
render + '</table>'
const markdown = emoji.emojify(render)
return markdown
}
const startGame = (meta) => {
const word = randomWord()
games[meta.room] = new Game(word, words)
sendMessage(meta.room, 'Le mot commence par la lettre `' + word.substr(0, 1) + '`')
sendMessage(meta.room, renderMatrix(games[meta.room]))
console.log(`${meta.sender} on room ${meta.room} starts the game (${word})`)
}
commander
.command('help')
.description('`help` : affiche l\'aide')
.action((meta) => {
let message = []
for (let command of commander.commands) {
message.push('* ' + command._description)
}
const text = message.join("\n")
sendMessage(meta.room, text);
})
commander
.command('start')
.description('`start` : démarrer une partie')
.action((meta) => {
if (games.hasOwnProperty(meta.room)) {
if (!games[meta.room].isFinish()) {
sendMessage(meta.room, 'Il y a une partie en cours !')
sendMessage(meta.room, 'Le mot commence par la lettre `' + games[meta.room].expectedWord.substr(0, 1) + '`')
return sendMessage(meta.room, renderMatrix(games[meta.room]))
}
}
startGame(meta)
})
commander
.command('restart')
.description('`restart` : démarrer une nouvelle partie')
.action((meta) => {
startGame(meta)
})
commander
.command('test [word]')
.description('`test mot` : tester le mot `mot`')
.action((meta, word) => {
if (games.hasOwnProperty(meta.room)) {
if (games[meta.room].isFinish()) {
return sendMessage(meta.room, 'La partie est terminée. Taper `restart` pour en lancer une nouvelle.')
}
} else {
return sendMessage(meta.room, 'Il n\'y pas de partie en cours. Taper `help` pour afficher l\'aide.')
}
const state = games[meta.room].tryWord(word)
if (!state) {
return sendMessage(meta.room, 'Le mot de ne fait pas la bonne longueur ou n\'apparait pas dans le dictionnaire.')
}
if (games[meta.room].isWon()) {
return sendMessage(meta.room, 'Bravo ! Tu as découvert le mot !')
}
if (games[meta.room].isFinish()) {
const expectedWord = games[meta.room].expectedWord
sendMessage(meta.room, `Tes propositions sont épuisées… Le mot a deviner était \`${expectedWord}\``)
}
sendMessage(meta.room, renderMatrix(games[meta.room]))
})
AutojoinRoomsMixin.setupOnClient(client)
client
.start()
.then(() => console.log('Client started'))
.catch(() => {
console.error('An error occurred')
})
client.on('room.message', (roomId, event) => {
if (!event['content']) {
return
}
const meta = {
sender: event['sender'],
body: event['content']['body'],
room: roomId,
}
commander.parse(meta.body.toLowerCase(), meta)
})

3541
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

21
package.json Normal file
View File

@ -0,0 +1,21 @@
{
"name": "matrix-motus-bot",
"version": "1.0.0",
"description": "",
"main": "index.js",
"type": "module",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"bot-commander": "^1.1.2",
"log-timestamp": "^0.3.0",
"matrix-bot-sdk": "^0.6.0-beta.4",
"node-emoji": "^1.11.0",
"showdown": "^1.9.1",
"typescript": "^4.5.5",
"unique-random-array": "^3.0.0"
}
}