@k4la/library 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,164 @@
1
+ /**
2
+ * @file lib/utils/tictactoe.js
3
+ * @description Utility functions for managing Tic Tac Toe game logic
4
+ * @author K4lameety
5
+ */
6
+
7
+ const jimp = require('jimp')
8
+
9
+ const gameData = {}
10
+
11
+ const createSession = (id) => {
12
+ const grid = [0, 0, 0, 0, 0, 0, 0, 0, 0]
13
+
14
+ gameData[id] = {
15
+ id: id,
16
+ status: 'waiting',
17
+ players: [],
18
+ grid: grid,
19
+ turn: 0,
20
+ winner: null,
21
+ winningLine: []
22
+ }
23
+ return gameData[id]
24
+ }
25
+
26
+ const getSession = (id) => gameData[id] || false
27
+ const deleteSession = (id) => delete gameData[id]
28
+
29
+ const userJoin = (id, sender, name) => {
30
+ let session = getSession(id)
31
+ if (!session) session = createSession(id)
32
+ if (session.status === 'playing') return 'is_playing'
33
+ if (session.players.some(p => p.id === sender)) return 'already_joined'
34
+ if (session.players.length >= 2) return 'room_full'
35
+
36
+ let marker = session.players.length === 0 ? 'X' : 'O'
37
+ session.players.push({ id: sender, name: name, marker: marker })
38
+
39
+ if (session.players.length === 2) {
40
+ session.status = 'playing'
41
+ }
42
+
43
+ return 'success'
44
+ }
45
+
46
+ const fillBoard = (id, sender, index) => {
47
+ let session = getSession(id)
48
+ if (!session || session.status !== 'playing') return { status: 'error', msg: 'Invalid session' }
49
+
50
+ let playerIdx = session.players.findIndex(p => p.id === sender)
51
+ if (playerIdx !== session.turn) return { status: 'error', msg: 'Not your turn!' }
52
+
53
+ let idx = index - 1
54
+ if (idx < 0 || idx > 8) return { status: 'error', msg: 'Position 1-9' }
55
+ if (session.grid[idx] !== 0) return { status: 'error', msg: 'Box already filled' }
56
+
57
+ session.grid[idx] = session.players[playerIdx].marker
58
+
59
+ let win = checkWin(session.grid)
60
+ if (win) {
61
+ session.winner = session.players[playerIdx]
62
+ session.winningLine = win.line
63
+ return { status: 'win', winner: session.winner, line: win.line }
64
+ }
65
+
66
+ if (session.grid.every(x => x !== 0)) {
67
+ return { status: 'draw' }
68
+ }
69
+
70
+ session.turn = session.turn === 0 ? 1 : 0
71
+
72
+ return { status: 'success', next: session.players[session.turn] }
73
+ }
74
+
75
+ const checkWin = (grid) => {
76
+ const wins = [
77
+ [0, 1, 2], [3, 4, 5], [6, 7, 8],
78
+ [0, 3, 6], [1, 4, 7], [2, 5, 8],
79
+ [0, 4, 8], [2, 4, 6]
80
+ ]
81
+
82
+ for (let line of wins) {
83
+ if (grid[line[0]] !== 0 &&
84
+ grid[line[0]] === grid[line[1]] &&
85
+ grid[line[1]] === grid[line[2]]) {
86
+ return { winner: grid[line[0]], line: line }
87
+ }
88
+ }
89
+ return null
90
+ }
91
+
92
+ const canvas = async (id, sock) => {
93
+ let session = getSession(id)
94
+ if (!session) return null
95
+
96
+ let bg = new jimp(500, 600, 0xFFFFFFFF)
97
+
98
+ let font = await jimp.loadFont(jimp.FONT_SANS_32_BLACK)
99
+ let fontSmall = await jimp.loadFont(jimp.FONT_SANS_16_BLACK)
100
+
101
+ bg.scan(166, 50, 5, 400, (x, y, idx) => { bg.bitmap.data[idx] = 0; bg.bitmap.data[idx+1] = 0; bg.bitmap.data[idx+2] = 0; bg.bitmap.data[idx+3] = 255 })
102
+ bg.scan(332, 50, 5, 400, (x, y, idx) => { bg.bitmap.data[idx] = 0; bg.bitmap.data[idx+1] = 0; bg.bitmap.data[idx+2] = 0; bg.bitmap.data[idx+3] = 255 })
103
+
104
+ bg.scan(50, 183, 400, 5, (x, y, idx) => { bg.bitmap.data[idx] = 0; bg.bitmap.data[idx+1] = 0; bg.bitmap.data[idx+2] = 0; bg.bitmap.data[idx+3] = 255 })
105
+ bg.scan(50, 316, 400, 5, (x, y, idx) => { bg.bitmap.data[idx] = 0; bg.bitmap.data[idx+1] = 0; bg.bitmap.data[idx+2] = 0; bg.bitmap.data[idx+3] = 255 })
106
+
107
+ const coords = [
108
+ [50, 50], [216, 50], [382, 50],
109
+ [50, 183], [216, 183], [382, 183],
110
+ [50, 316], [216, 316], [382, 316]
111
+ ]
112
+
113
+ for (let i = 0; i < 9; i++) {
114
+ if (session.grid[i] === 0) {
115
+ bg.print(fontSmall, coords[i][0] + 5, coords[i][1] + 5, `${i+1}`)
116
+ } else {
117
+ let marker = session.grid[i]
118
+ let player = session.players.find(p => p.marker === marker)
119
+
120
+ try {
121
+ let ppUrl = await sock.profilePictureUrl(player.id, 'image').catch(() => 'https://telegra.ph/file/24fa902ead26340f3df2c.png')
122
+ let pp = await jimp.read(ppUrl)
123
+
124
+ pp.resize(100, 100).circle()
125
+ bg.composite(pp, coords[i][0] + 30, coords[i][1] + 15)
126
+ } catch (e) {
127
+ bg.print(font, coords[i][0] + 60, coords[i][1] + 40, marker)
128
+ }
129
+ }
130
+ }
131
+
132
+ if (session.winningLine && session.winningLine.length > 0) {
133
+ let redBox = new jimp(160, 130, 0xFF000055)
134
+
135
+ for (let idx of session.winningLine) {
136
+ let x = coords[idx][0]
137
+ let y = coords[idx][1]
138
+ bg.composite(redBox, x, y)
139
+ }
140
+ }
141
+
142
+ let p1Name = session.players[0] ? session.players[0].name : "-"
143
+ let p2Name = session.players[1] ? session.players[1].name : "-"
144
+
145
+ bg.print(fontSmall, 20, 520, `X: ${p1Name}`)
146
+ bg.print(fontSmall, 20, 550, `O: ${p2Name}`)
147
+
148
+ let turnName = session.status === 'playing'
149
+ ? (session.winner ? `WINNER: ${session.winner.name}` : `Turn: ${session.players[session.turn].name}`)
150
+ : "Waiting for Players..."
151
+
152
+ bg.print(font, 80, 470, turnName)
153
+
154
+ return await bg.getBufferAsync(jimp.MIME_JPEG)
155
+ }
156
+
157
+ module.exports = {
158
+ createSession,
159
+ userJoin,
160
+ fillBoard,
161
+ getSession,
162
+ deleteSession,
163
+ canvas
164
+ }
@@ -0,0 +1,181 @@
1
+ /**
2
+ * @file lib/utils/ulartangga.js
3
+ * @description Snake and Ladder game utilities
4
+ * @author K4lameety
5
+ */
6
+
7
+ const jimp = require('jimp')
8
+
9
+ const gameData = {}
10
+
11
+ const BOARD_URL = "https://raw.githubusercontent.com/K4lameety/game-assets/main/snake-and-ladder/board.png"
12
+
13
+ const MAP_CONFIG = {
14
+ 4: 14,
15
+ 9: 31,
16
+ 20: 38,
17
+ 28: 84,
18
+ 40: 59,
19
+ 51: 67,
20
+ 63: 81,
21
+ 71: 91,
22
+
23
+ 17: 7,
24
+ 54: 34,
25
+ 62: 19,
26
+ 64: 60,
27
+ 87: 24,
28
+ 93: 73,
29
+ 95: 75,
30
+ 99: 78
31
+ }
32
+
33
+ const createSession = (id) => {
34
+ gameData[id] = {
35
+ id: id,
36
+ status: 'waiting',
37
+ players: [],
38
+ turn: 0
39
+ }
40
+ return gameData[id]
41
+ }
42
+
43
+ const getSession = (id) => gameData[id] || false
44
+ const deleteSession = (id) => delete gameData[id]
45
+
46
+ const userJoin = (id, sender, name) => {
47
+ let session = getSession(id)
48
+ if (!session) session = createSession(id)
49
+ if (session.status === 'playing') return 'is_playing'
50
+ if (session.players.some(p => p.id === sender)) return 'already_joined'
51
+ if (session.players.length >= 4) return 'room_full'
52
+
53
+ const colors = [0xFF0000FF, 0x00FF00FF, 0x0000FFFF, 0xFFFF00FF]
54
+ let color = colors[session.players.length]
55
+
56
+ session.players.push({ id: sender, name: name, color: color, pos: 1 })
57
+ return 'success'
58
+ }
59
+
60
+ const startGame = (id) => {
61
+ let session = getSession(id)
62
+ if (!session || session.players.length < 2) return false
63
+ session.status = 'playing'
64
+ return true
65
+ }
66
+
67
+ const rollDice = (id, sender) => {
68
+ let session = getSession(id)
69
+ if (!session || session.status !== 'playing') return { status: 'error', msg: 'Session invalid' }
70
+
71
+ let playerIdx = session.players.findIndex(p => p.id === sender)
72
+ if (playerIdx !== session.turn) return { status: 'error', msg: 'Not your turn!' }
73
+
74
+ let player = session.players[playerIdx]
75
+
76
+ let dice = Math.floor(Math.random() * 6) + 1
77
+ let oldPos = player.pos
78
+ let newPos = oldPos + dice
79
+
80
+ if (newPos > 100) {
81
+ newPos = 100 - (newPos - 100)
82
+ }
83
+
84
+ let message = `Dice: ${dice}. Moving to ${newPos}.`
85
+
86
+ if (MAP_CONFIG[newPos]) {
87
+ let dest = MAP_CONFIG[newPos]
88
+ if (dest > newPos) message += `\nClimbed LADDER! To ${dest}`
89
+ else message += `\nBitten by SNAKE! Down to ${dest}`
90
+ newPos = dest
91
+ }
92
+
93
+ player.pos = newPos
94
+
95
+ if (newPos === 100) {
96
+ return { status: 'win', winner: player }
97
+ }
98
+
99
+ session.turn = (session.turn + 1) % session.players.length
100
+
101
+ return { status: 'success', player, dice, message, next: session.players[session.turn] }
102
+ }
103
+
104
+ const canvas = async (id, sock) => {
105
+ let session = getSession(id)
106
+ if (!session) return null
107
+
108
+ let bg
109
+ try {
110
+ bg = await jimp.read(BOARD_URL)
111
+ bg.resize(600, 600)
112
+ } catch (e) {
113
+ console.log('Failed to load board image from URL')
114
+ bg = new jimp(600, 600, 0xFFFFFFFF)
115
+ }
116
+
117
+ let layout = new jimp(600, 700, 0xFFFFFFFF)
118
+ layout.composite(bg, 0, 0)
119
+
120
+ let font = await jimp.loadFont(jimp.FONT_SANS_16_BLACK)
121
+ let fontBig = await jimp.loadFont(jimp.FONT_SANS_32_BLACK)
122
+
123
+ for (let p of session.players) {
124
+ let { x, y } = getCoord(p.pos)
125
+
126
+ try {
127
+ let ppUrl = await sock.profilePictureUrl(p.id, 'image').catch(() => 'https://telegra.ph/file/24fa902ead26340f3df2c.png')
128
+ let pp = await jimp.read(ppUrl)
129
+ pp.resize(40, 40).circle()
130
+
131
+ let border = new jimp(44, 44, p.color)
132
+ border.circle()
133
+ border.composite(pp, 2, 2)
134
+
135
+ let stackIndex = session.players.filter(pl => pl.pos === p.pos && session.players.indexOf(pl) < session.players.indexOf(p)).length
136
+ let offsetX = stackIndex * 10
137
+ let offsetY = stackIndex * 5
138
+
139
+ layout.composite(border, x + 8 + offsetX, y + 8 + offsetY)
140
+
141
+ } catch (e) { }
142
+ }
143
+
144
+ let turnName = session.status === 'playing'
145
+ ? `Turn: ${session.players[session.turn].name}`
146
+ : "Waiting for Players..."
147
+
148
+ let statusPos = session.players.map(p => `${p.name.slice(0,5)}: ${p.pos}`).join(' | ')
149
+
150
+ layout.print(fontBig, 20, 610, turnName)
151
+ layout.print(font, 20, 660, statusPos)
152
+
153
+ return await layout.getBufferAsync(jimp.MIME_JPEG)
154
+ }
155
+
156
+ const getCoord = (pos) => {
157
+ if (pos < 1) pos = 1
158
+ if (pos > 100) pos = 100
159
+
160
+ let row = Math.floor((pos - 1) / 10)
161
+ let col = (pos - 1) % 10
162
+
163
+ if (row % 2 !== 0) {
164
+ col = 9 - col
165
+ }
166
+
167
+ let x = col * 60
168
+ let y = (9 - row) * 60
169
+
170
+ return { x, y }
171
+ }
172
+
173
+ module.exports = {
174
+ createSession,
175
+ userJoin,
176
+ startGame,
177
+ rollDice,
178
+ getSession,
179
+ deleteSession,
180
+ canvas
181
+ }
package/lib/uno.js ADDED
@@ -0,0 +1,250 @@
1
+ /**
2
+ * @file lib/utils/uno.js
3
+ * @description Utility functions for managing Uno game logic
4
+ * @author K4lameety
5
+ */
6
+
7
+ const fs = require('fs')
8
+ const jimp = require('jimp')
9
+
10
+ const GITHUB_BASE_URL = "https://raw.githubusercontent.com/K4lameety/game-assets/main/uno/"
11
+
12
+ const gameData = {}
13
+
14
+ const shuffle = (array) => {
15
+ for (let i = array.length - 1; i > 0; i--) {
16
+ const j = Math.floor(Math.random() * (i + 1));
17
+ [array[i], array[j]] = [array[j], array[i]]
18
+ }
19
+ return array
20
+ }
21
+
22
+ const mod = (n, m) => ((n % m) + m) % m
23
+
24
+ const generateDeck = () => {
25
+ const colors = ['red', 'green', 'blue', 'yellow']
26
+ const numbers = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9']
27
+ const actions = ['skip', 'reverse', 'draw2']
28
+ const wilds = ['wild', 'wild4']
29
+
30
+ let deck = []
31
+
32
+ colors.forEach(color => {
33
+ numbers.forEach(num => {
34
+ let count = (num === '0') ? 1 : 2
35
+ for (let i = 0; i < count; i++) {
36
+ deck.push({
37
+ color: color,
38
+ value: num,
39
+ id: `${num}_card_${color}`
40
+ })
41
+ }
42
+ })
43
+
44
+ actions.forEach(act => {
45
+ for (let i = 0; i < 2; i++) {
46
+ let idName = ''
47
+ if (act === 'skip') idName = `special_block_card_${color}`
48
+ if (act === 'reverse') idName = `special_revert_card_${color}`
49
+ if (act === 'draw2') idName = `special_2_card_${color}`
50
+
51
+ deck.push({
52
+ color: color,
53
+ value: act,
54
+ id: idName
55
+ })
56
+ }
57
+ })
58
+ })
59
+
60
+ wilds.forEach(wild => {
61
+ for (let i = 0; i < 4; i++) {
62
+ let idName = ''
63
+ if (wild === 'wild') idName = 'special_colorful_card'
64
+ if (wild === 'wild4') idName = 'special_4_card'
65
+
66
+ deck.push({
67
+ color: 'black',
68
+ value: wild,
69
+ id: idName
70
+ })
71
+ }
72
+ })
73
+
74
+ return shuffle(deck)
75
+ }
76
+
77
+ const createSession = (id) => {
78
+ gameData[id] = {
79
+ id: id,
80
+ status: 'waiting',
81
+ players: [],
82
+ deck: [],
83
+ pile: {},
84
+ turn: 0,
85
+ direction: 1,
86
+ currentColor: ''
87
+ }
88
+ return gameData[id]
89
+ }
90
+
91
+ const getSession = (id) => gameData[id] || false
92
+ const deleteSession = (id) => delete gameData[id]
93
+
94
+ const userJoin = (id, sender, name) => {
95
+ let session = getSession(id)
96
+ if (!session) session = createSession(id)
97
+ if (session.status === 'playing') return 'is_playing'
98
+ if (session.players.some(p => p.id === sender)) return 'already_joined'
99
+ session.players.push({ id: sender, name: name, hand: [] })
100
+ return 'success'
101
+ }
102
+
103
+ const startGame = (id) => {
104
+ let session = getSession(id)
105
+ if (!session || session.players.length < 2) return false
106
+
107
+ session.status = 'playing'
108
+ session.deck = generateDeck()
109
+ session.players.forEach(p => { p.hand = session.deck.splice(0, 7) })
110
+
111
+ let firstCard
112
+ do {
113
+ session.deck = shuffle(session.deck)
114
+ firstCard = session.deck.pop()
115
+ } while (firstCard.color === 'black')
116
+
117
+ session.pile = firstCard
118
+ session.currentColor = firstCard.color
119
+
120
+ return session
121
+ }
122
+
123
+ const playCard = (id, sender, index, wildColor = null) => {
124
+ let session = getSession(id)
125
+ if (!session) return { status: 'error', msg: 'No session' }
126
+
127
+ let player = session.players[session.turn]
128
+ if (player.id !== sender) return { status: 'error', msg: 'Not your turn' }
129
+ if (!player.hand[index]) return { status: 'error', msg: 'Card not found' }
130
+
131
+ let card = player.hand[index]
132
+ let top = session.pile
133
+
134
+ let valid = false
135
+ if (card.color === session.currentColor) valid = true
136
+ if (card.value === top.value) valid = true
137
+ if (card.color === 'black') valid = true
138
+
139
+ if (!valid) return { status: 'error', msg: 'Card does not match' }
140
+
141
+ player.hand.splice(index, 1)
142
+ session.pile = card
143
+
144
+ if (card.color === 'black') {
145
+ if (wildColor) wildColor = wildColor.toLowerCase()
146
+ if (!wildColor || !['red', 'green', 'blue', 'yellow'].includes(wildColor)) {
147
+ player.hand.push(card)
148
+ return { status: 'error', msg: 'Please specify the color! Example: .uno play 1 red' }
149
+ }
150
+ session.currentColor = wildColor
151
+ } else {
152
+ session.currentColor = card.color
153
+ }
154
+
155
+ if (player.hand.length === 0) return { status: 'win', winner: player }
156
+
157
+ let skip = 0
158
+
159
+ if (card.value === 'reverse') {
160
+ if (session.players.length === 2) skip = 1
161
+ else session.direction *= -1
162
+ }
163
+
164
+ if (card.value === 'skip') skip = 1
165
+
166
+ if (card.value === 'draw2') {
167
+ skip = 1
168
+ let victimIndex = mod(session.turn + session.direction, session.players.length)
169
+ session.players[victimIndex].hand.push(...session.deck.splice(0, 2))
170
+ }
171
+
172
+ if (card.value === 'wild4') {
173
+ skip = 1
174
+ let victimIndex = mod(session.turn + session.direction, session.players.length)
175
+ session.players[victimIndex].hand.push(...session.deck.splice(0, 4))
176
+ }
177
+
178
+ session.turn = mod(session.turn + (session.direction * (1 + skip)), session.players.length)
179
+ if (session.deck.length < 5) session.deck = [...session.deck, ...generateDeck()]
180
+
181
+ return { status: 'success', next: session.players[session.turn] }
182
+ }
183
+
184
+ const drawCard = (id, sender) => {
185
+ let session = getSession(id)
186
+ let player = session.players[session.turn]
187
+ if (player.id !== sender) return { status: 'error' }
188
+
189
+ let card = session.deck.pop()
190
+ player.hand.push(card)
191
+ session.turn = mod(session.turn + session.direction, session.players.length)
192
+
193
+ return { status: 'success', card: card, next: session.players[session.turn] }
194
+ }
195
+
196
+ const canvas = async (id, sender) => {
197
+ let session = getSession(id)
198
+ if (!session) return null
199
+ let player = session.players.find(p => p.id === sender)
200
+ if (!player) return null
201
+
202
+ let bg = new jimp(1200, 800, 0x222222FF)
203
+ let font = await jimp.loadFont(jimp.FONT_SANS_32_WHITE)
204
+
205
+ let pileUrl = `${GITHUB_BASE_URL}${session.pile.id}.png`
206
+
207
+ try {
208
+ let pileImg = await jimp.read(pileUrl)
209
+ pileImg.resize(180, 260)
210
+ bg.composite(pileImg, 510, 200)
211
+ } catch (e) {
212
+ bg.print(font, 510, 250, session.pile.value)
213
+ }
214
+
215
+ bg.print(font, 50, 50, `Color: ${session.currentColor.toUpperCase()}`)
216
+ bg.print(font, 50, 100, `Player: ${session.players[session.turn].name}`)
217
+ bg.print(font, 900, 50, `Direction: ${session.direction === 1 ? '>>' : '<<'}`)
218
+
219
+ let x = 50
220
+ let y = 500
221
+
222
+ for (let i = 0; i < player.hand.length; i++) {
223
+ let card = player.hand[i]
224
+ let cardUrl = `${GITHUB_BASE_URL}${card.id}.png`
225
+
226
+ try {
227
+ let cardImg = await jimp.read(cardUrl)
228
+ cardImg.resize(120, 175)
229
+ bg.composite(cardImg, x, y)
230
+ bg.print(font, x + 45, y - 40, `${i + 1}`)
231
+
232
+ x += 100
233
+ if (x > 1050) { x = 50; y += 190 }
234
+ } catch (e) {
235
+ bg.print(font, x, y, card.value)
236
+ }
237
+ }
238
+
239
+ return await bg.getBufferAsync(jimp.MIME_JPEG)
240
+ }
241
+
242
+ module.exports = {
243
+ userJoin,
244
+ startGame,
245
+ playCard,
246
+ drawCard,
247
+ getSession,
248
+ deleteSession,
249
+ canvas
250
+ }