clarityxo-sdk 0.1.0 → 0.2.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.
- package/README.md +38 -0
- package/dist/chunk-ETEDBK3G.mjs +348 -0
- package/dist/cli/index.js +188 -3
- package/dist/cli/index.mjs +172 -4
- package/dist/index.d.mts +21 -1
- package/dist/index.d.ts +21 -1
- package/dist/index.js +88 -0
- package/dist/index.mjs +63 -1
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -57,6 +57,7 @@ These require `senderKey` and `senderAddress` in config.
|
|
|
57
57
|
- `submitResult(result: GameResult): Promise<void>` - Submit a game result
|
|
58
58
|
- `syncLeaderboard(): Promise<void>` - Sync leaderboard data
|
|
59
59
|
- `healthCheck(): Promise<boolean>` - Check if leaderboard API is healthy
|
|
60
|
+
- `getPlayerStats(playerAddress: string, month?: string): Promise<PlayerStats | null>` - Get statistics for a specific player
|
|
60
61
|
|
|
61
62
|
### Types
|
|
62
63
|
|
|
@@ -69,6 +70,7 @@ These require `senderKey` and `senderAddress` in config.
|
|
|
69
70
|
- `LeaderboardEntry`: { player, wins, losses, draws, points, rank }
|
|
70
71
|
- `LeaderboardMonth`: { month, entries }
|
|
71
72
|
- `GameResult`: { player, outcome, month }
|
|
73
|
+
- `PlayerStats`: { player, wins, losses, draws, points, rank, totalGames, winRate }
|
|
72
74
|
|
|
73
75
|
## CLI
|
|
74
76
|
|
|
@@ -129,6 +131,30 @@ Display the leaderboard for a month.
|
|
|
129
131
|
clarityxo leaderboard --month 2023-10 --contract ST1PQHQKVVA1MGKKKSQCXDTEYXSGHCV66VR89BS1
|
|
130
132
|
```
|
|
131
133
|
|
|
134
|
+
#### `move <row> <col>`
|
|
135
|
+
|
|
136
|
+
Make a move on the board. Requires sender key and address.
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
clarityxo move 1 2 --contract ST1PQHQKVVA1MGKKKSQCXDTEYXSGHCV66VR89BS1 --key <private-key> --address <address>
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
#### `start`
|
|
143
|
+
|
|
144
|
+
Start a new game. Requires sender key and address.
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
clarityxo start --contract ST1PQHQKVVA1MGKKKSQCXDTEYXSGHCV66VR89BS1 --key <private-key> --address <address>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
#### `resign`
|
|
151
|
+
|
|
152
|
+
Resign the current game. Requires sender key and address.
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
clarityxo resign --contract ST1PQHQKVVA1MGKKKSQCXDTEYXSGHCV66VR89BS1 --key <private-key> --address <address>
|
|
156
|
+
```
|
|
157
|
+
|
|
132
158
|
## Network Configuration
|
|
133
159
|
|
|
134
160
|
- **Mainnet**: Use 'mainnet' network, mainnet contract addresses
|
|
@@ -149,6 +175,18 @@ try {
|
|
|
149
175
|
}
|
|
150
176
|
```
|
|
151
177
|
|
|
178
|
+
## Utility Functions
|
|
179
|
+
|
|
180
|
+
The SDK includes utility functions for board analysis:
|
|
181
|
+
|
|
182
|
+
- `checkWin(board, player)` - Check if a player has won
|
|
183
|
+
- `isBoardFull(board)` - Check if the board is full
|
|
184
|
+
- `isDraw(board)` - Check if the game is a draw
|
|
185
|
+
- `getAvailableMoves(board)` - Get list of available moves
|
|
186
|
+
- `getBoardString(board)` - Convert board to string representation
|
|
187
|
+
- `cloneBoard(board)` - Create a deep copy of the board
|
|
188
|
+
- `makeMoveOnBoard(board, row, col, player)` - Make a move and return new board
|
|
189
|
+
|
|
152
190
|
## Contributing
|
|
153
191
|
|
|
154
192
|
1. Fork the repository
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
// src/constants.ts
|
|
2
|
+
var CONTRACT_NAME = "tictactoe";
|
|
3
|
+
var CONTRACT_ADDRESS = "SP30VGN68PSGVWGNMD0HH2WQMM5T486EK3YGP7Z3Y.clarity-xo-game";
|
|
4
|
+
var MAINNET_API = "https://api.mainnet.hiro.so";
|
|
5
|
+
var TESTNET_API = "https://api.testnet.hiro.so";
|
|
6
|
+
var DEFAULT_LEADERBOARD_API = "https://clarityxo.onrender.com";
|
|
7
|
+
var CONTRACT_FUNCTIONS = {
|
|
8
|
+
START_NEW_GAME: "start-new-game",
|
|
9
|
+
MAKE_MOVE: "make-move",
|
|
10
|
+
RESIGN_GAME: "resign-game",
|
|
11
|
+
GET_BOARD_STATE: "get-board-state",
|
|
12
|
+
GET_GAME_STATUS: "get-game-status",
|
|
13
|
+
GET_WINNER: "get-winner",
|
|
14
|
+
GET_CURRENT_TURN: "get-current-turn",
|
|
15
|
+
IS_VALID_MOVE: "is-valid-move"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// src/contract/read.ts
|
|
19
|
+
import { callReadOnlyFunction, uintCV } from "@stacks/transactions";
|
|
20
|
+
|
|
21
|
+
// src/utils/network.ts
|
|
22
|
+
import { StacksMainnet, StacksTestnet } from "@stacks/network";
|
|
23
|
+
function getStacksNetwork(network) {
|
|
24
|
+
return network === "mainnet" ? new StacksMainnet() : new StacksTestnet();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/utils/cv.ts
|
|
28
|
+
function parseBoardCV(cv) {
|
|
29
|
+
const rows = cv;
|
|
30
|
+
const board = [
|
|
31
|
+
[null, null, null],
|
|
32
|
+
[null, null, null],
|
|
33
|
+
[null, null, null]
|
|
34
|
+
];
|
|
35
|
+
for (let i = 0; i < 3; i++) {
|
|
36
|
+
const row = rows.list[i].list;
|
|
37
|
+
for (let j = 0; j < 3; j++) {
|
|
38
|
+
const cell = row[j];
|
|
39
|
+
if (cell.type === "some") {
|
|
40
|
+
board[i][j] = cell.value.value;
|
|
41
|
+
} else {
|
|
42
|
+
board[i][j] = null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return board;
|
|
47
|
+
}
|
|
48
|
+
function parseGameStatusCV(cv) {
|
|
49
|
+
const value = cv.value;
|
|
50
|
+
if (value === "active") return "active";
|
|
51
|
+
if (value === "finished") return "finished";
|
|
52
|
+
if (value === "not-started") return "not-started";
|
|
53
|
+
throw new Error(`Invalid game status: ${value}`);
|
|
54
|
+
}
|
|
55
|
+
function parseWinnerCV(cv) {
|
|
56
|
+
const value = cv.value;
|
|
57
|
+
if (value === "player") return "player";
|
|
58
|
+
if (value === "ai") return "ai";
|
|
59
|
+
if (value === "draw") return "draw";
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
function parseTurnCV(cv) {
|
|
63
|
+
const value = cv.value;
|
|
64
|
+
if (value === "player") return "player";
|
|
65
|
+
if (value === "ai") return "ai";
|
|
66
|
+
throw new Error(`Invalid turn: ${value}`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// src/contract/read.ts
|
|
70
|
+
async function getBoardState(config) {
|
|
71
|
+
const network = getStacksNetwork(config.network);
|
|
72
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
73
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
74
|
+
const cv = await callReadOnlyFunction({
|
|
75
|
+
network,
|
|
76
|
+
contractAddress,
|
|
77
|
+
contractName,
|
|
78
|
+
functionName: CONTRACT_FUNCTIONS.GET_BOARD_STATE,
|
|
79
|
+
functionArgs: [],
|
|
80
|
+
senderAddress: contractAddress
|
|
81
|
+
// arbitrary
|
|
82
|
+
});
|
|
83
|
+
return parseBoardCV(cv);
|
|
84
|
+
}
|
|
85
|
+
async function getGameStatus(config) {
|
|
86
|
+
const network = getStacksNetwork(config.network);
|
|
87
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
88
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
89
|
+
const cv = await callReadOnlyFunction({
|
|
90
|
+
network,
|
|
91
|
+
contractAddress,
|
|
92
|
+
contractName,
|
|
93
|
+
functionName: CONTRACT_FUNCTIONS.GET_GAME_STATUS,
|
|
94
|
+
functionArgs: [],
|
|
95
|
+
senderAddress: contractAddress
|
|
96
|
+
});
|
|
97
|
+
return parseGameStatusCV(cv);
|
|
98
|
+
}
|
|
99
|
+
async function getWinner(config) {
|
|
100
|
+
const network = getStacksNetwork(config.network);
|
|
101
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
102
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
103
|
+
const cv = await callReadOnlyFunction({
|
|
104
|
+
network,
|
|
105
|
+
contractAddress,
|
|
106
|
+
contractName,
|
|
107
|
+
functionName: CONTRACT_FUNCTIONS.GET_WINNER,
|
|
108
|
+
functionArgs: [],
|
|
109
|
+
senderAddress: contractAddress
|
|
110
|
+
});
|
|
111
|
+
return parseWinnerCV(cv);
|
|
112
|
+
}
|
|
113
|
+
async function getCurrentTurn(config) {
|
|
114
|
+
const network = getStacksNetwork(config.network);
|
|
115
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
116
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
117
|
+
const cv = await callReadOnlyFunction({
|
|
118
|
+
network,
|
|
119
|
+
contractAddress,
|
|
120
|
+
contractName,
|
|
121
|
+
functionName: CONTRACT_FUNCTIONS.GET_CURRENT_TURN,
|
|
122
|
+
functionArgs: [],
|
|
123
|
+
senderAddress: contractAddress
|
|
124
|
+
});
|
|
125
|
+
return parseTurnCV(cv);
|
|
126
|
+
}
|
|
127
|
+
async function isValidMove(config, row, col) {
|
|
128
|
+
const network = getStacksNetwork(config.network);
|
|
129
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
130
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
131
|
+
const cv = await callReadOnlyFunction({
|
|
132
|
+
network,
|
|
133
|
+
contractAddress,
|
|
134
|
+
contractName,
|
|
135
|
+
functionName: CONTRACT_FUNCTIONS.IS_VALID_MOVE,
|
|
136
|
+
functionArgs: [uintCV(row), uintCV(col)],
|
|
137
|
+
senderAddress: contractAddress
|
|
138
|
+
});
|
|
139
|
+
return cv.value;
|
|
140
|
+
}
|
|
141
|
+
async function getFullGameState(config) {
|
|
142
|
+
const [board, status, winner, currentTurn] = await Promise.all([
|
|
143
|
+
getBoardState(config),
|
|
144
|
+
getGameStatus(config),
|
|
145
|
+
getWinner(config),
|
|
146
|
+
getCurrentTurn(config)
|
|
147
|
+
]);
|
|
148
|
+
return { board, status, winner, currentTurn };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// src/contract/write.ts
|
|
152
|
+
import { makeContractCall, broadcastTransaction, uintCV as uintCV2 } from "@stacks/transactions";
|
|
153
|
+
async function startNewGame(config) {
|
|
154
|
+
if (!config.senderKey || !config.senderAddress) {
|
|
155
|
+
throw new Error("senderKey and senderAddress are required for write operations");
|
|
156
|
+
}
|
|
157
|
+
const network = getStacksNetwork(config.network);
|
|
158
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
159
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
160
|
+
const tx = await makeContractCall({
|
|
161
|
+
network,
|
|
162
|
+
contractAddress,
|
|
163
|
+
contractName,
|
|
164
|
+
functionName: CONTRACT_FUNCTIONS.START_NEW_GAME,
|
|
165
|
+
functionArgs: [],
|
|
166
|
+
senderKey: config.senderKey,
|
|
167
|
+
anchorMode: "any"
|
|
168
|
+
});
|
|
169
|
+
const broadcastResponse = await broadcastTransaction(tx, network);
|
|
170
|
+
return { txId: broadcastResponse.txid };
|
|
171
|
+
}
|
|
172
|
+
async function makeMove(config, row, col) {
|
|
173
|
+
if (!config.senderKey || !config.senderAddress) {
|
|
174
|
+
throw new Error("senderKey and senderAddress are required for write operations");
|
|
175
|
+
}
|
|
176
|
+
const network = getStacksNetwork(config.network);
|
|
177
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
178
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
179
|
+
const tx = await makeContractCall({
|
|
180
|
+
network,
|
|
181
|
+
contractAddress,
|
|
182
|
+
contractName,
|
|
183
|
+
functionName: CONTRACT_FUNCTIONS.MAKE_MOVE,
|
|
184
|
+
functionArgs: [uintCV2(row), uintCV2(col)],
|
|
185
|
+
senderKey: config.senderKey,
|
|
186
|
+
anchorMode: "any"
|
|
187
|
+
});
|
|
188
|
+
const broadcastResponse = await broadcastTransaction(tx, network);
|
|
189
|
+
return { txId: broadcastResponse.txid };
|
|
190
|
+
}
|
|
191
|
+
async function resignGame(config) {
|
|
192
|
+
if (!config.senderKey || !config.senderAddress) {
|
|
193
|
+
throw new Error("senderKey and senderAddress are required for write operations");
|
|
194
|
+
}
|
|
195
|
+
const network = getStacksNetwork(config.network);
|
|
196
|
+
const contractName = config.contractName || CONTRACT_NAME;
|
|
197
|
+
const contractAddress = config.contractAddress || CONTRACT_ADDRESS;
|
|
198
|
+
const tx = await makeContractCall({
|
|
199
|
+
network,
|
|
200
|
+
contractAddress,
|
|
201
|
+
contractName,
|
|
202
|
+
functionName: CONTRACT_FUNCTIONS.RESIGN_GAME,
|
|
203
|
+
functionArgs: [],
|
|
204
|
+
senderKey: config.senderKey,
|
|
205
|
+
anchorMode: "any"
|
|
206
|
+
});
|
|
207
|
+
const broadcastResponse = await broadcastTransaction(tx, network);
|
|
208
|
+
return { txId: broadcastResponse.txid };
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/leaderboard/api.ts
|
|
212
|
+
function getBaseUrl(config) {
|
|
213
|
+
return config.leaderboardApiUrl || DEFAULT_LEADERBOARD_API;
|
|
214
|
+
}
|
|
215
|
+
async function getLeaderboard(config, month) {
|
|
216
|
+
const baseUrl = getBaseUrl(config);
|
|
217
|
+
const response = await fetch(`${baseUrl}/api/leaderboard?month=${month}`);
|
|
218
|
+
if (!response.ok) {
|
|
219
|
+
throw new Error(`Failed to fetch leaderboard: ${response.statusText}`);
|
|
220
|
+
}
|
|
221
|
+
return response.json();
|
|
222
|
+
}
|
|
223
|
+
async function submitResult(config, result) {
|
|
224
|
+
const baseUrl = getBaseUrl(config);
|
|
225
|
+
const response = await fetch(`${baseUrl}/api/leaderboard/result`, {
|
|
226
|
+
method: "POST",
|
|
227
|
+
headers: { "Content-Type": "application/json" },
|
|
228
|
+
body: JSON.stringify(result)
|
|
229
|
+
});
|
|
230
|
+
if (!response.ok) {
|
|
231
|
+
throw new Error(`Failed to submit result: ${response.statusText}`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
async function syncLeaderboard(config) {
|
|
235
|
+
const baseUrl = getBaseUrl(config);
|
|
236
|
+
const response = await fetch(`${baseUrl}/api/sync`, {
|
|
237
|
+
method: "POST"
|
|
238
|
+
});
|
|
239
|
+
if (!response.ok) {
|
|
240
|
+
throw new Error(`Failed to sync leaderboard: ${response.statusText}`);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function healthCheck(config) {
|
|
244
|
+
const baseUrl = getBaseUrl(config);
|
|
245
|
+
try {
|
|
246
|
+
const response = await fetch(`${baseUrl}/health`);
|
|
247
|
+
return response.ok;
|
|
248
|
+
} catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function getPlayerStats(config, playerAddress, month) {
|
|
253
|
+
const currentMonth = month || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
254
|
+
const leaderboard = await getLeaderboard(config, currentMonth);
|
|
255
|
+
const entry = leaderboard.entries.find((e) => e.player === playerAddress);
|
|
256
|
+
if (!entry) {
|
|
257
|
+
return null;
|
|
258
|
+
}
|
|
259
|
+
const totalGames = entry.wins + entry.losses + entry.draws;
|
|
260
|
+
const winRate = totalGames > 0 ? entry.wins / totalGames * 100 : 0;
|
|
261
|
+
return {
|
|
262
|
+
...entry,
|
|
263
|
+
totalGames,
|
|
264
|
+
winRate: Math.round(winRate * 100) / 100
|
|
265
|
+
// Round to 2 decimal places
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// src/client.ts
|
|
270
|
+
var ClarityXOClient = class {
|
|
271
|
+
constructor(config) {
|
|
272
|
+
this.config = config;
|
|
273
|
+
}
|
|
274
|
+
config;
|
|
275
|
+
// Game state
|
|
276
|
+
getBoardState() {
|
|
277
|
+
return getBoardState(this.config);
|
|
278
|
+
}
|
|
279
|
+
getGameStatus() {
|
|
280
|
+
return getGameStatus(this.config);
|
|
281
|
+
}
|
|
282
|
+
getWinner() {
|
|
283
|
+
return getWinner(this.config);
|
|
284
|
+
}
|
|
285
|
+
getCurrentTurn() {
|
|
286
|
+
return getCurrentTurn(this.config);
|
|
287
|
+
}
|
|
288
|
+
isValidMove(row, col) {
|
|
289
|
+
return isValidMove(this.config, row, col);
|
|
290
|
+
}
|
|
291
|
+
getFullGameState() {
|
|
292
|
+
return getFullGameState(this.config);
|
|
293
|
+
}
|
|
294
|
+
// Transactions
|
|
295
|
+
startNewGame() {
|
|
296
|
+
return startNewGame(this.config);
|
|
297
|
+
}
|
|
298
|
+
makeMove(row, col) {
|
|
299
|
+
return makeMove(this.config, row, col);
|
|
300
|
+
}
|
|
301
|
+
resignGame() {
|
|
302
|
+
return resignGame(this.config);
|
|
303
|
+
}
|
|
304
|
+
// Leaderboard
|
|
305
|
+
getLeaderboard(month) {
|
|
306
|
+
return getLeaderboard(this.config, month);
|
|
307
|
+
}
|
|
308
|
+
submitResult(result) {
|
|
309
|
+
return submitResult(this.config, result);
|
|
310
|
+
}
|
|
311
|
+
syncLeaderboard() {
|
|
312
|
+
return syncLeaderboard(this.config);
|
|
313
|
+
}
|
|
314
|
+
healthCheck() {
|
|
315
|
+
return healthCheck(this.config);
|
|
316
|
+
}
|
|
317
|
+
getPlayerStats(playerAddress, month) {
|
|
318
|
+
return getPlayerStats(this.config, playerAddress, month);
|
|
319
|
+
}
|
|
320
|
+
};
|
|
321
|
+
function createClient(config) {
|
|
322
|
+
return new ClarityXOClient(config);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
export {
|
|
326
|
+
CONTRACT_NAME,
|
|
327
|
+
CONTRACT_ADDRESS,
|
|
328
|
+
MAINNET_API,
|
|
329
|
+
TESTNET_API,
|
|
330
|
+
DEFAULT_LEADERBOARD_API,
|
|
331
|
+
CONTRACT_FUNCTIONS,
|
|
332
|
+
getBoardState,
|
|
333
|
+
getGameStatus,
|
|
334
|
+
getWinner,
|
|
335
|
+
getCurrentTurn,
|
|
336
|
+
isValidMove,
|
|
337
|
+
getFullGameState,
|
|
338
|
+
startNewGame,
|
|
339
|
+
makeMove,
|
|
340
|
+
resignGame,
|
|
341
|
+
getLeaderboard,
|
|
342
|
+
submitResult,
|
|
343
|
+
syncLeaderboard,
|
|
344
|
+
healthCheck,
|
|
345
|
+
getPlayerStats,
|
|
346
|
+
ClarityXOClient,
|
|
347
|
+
createClient
|
|
348
|
+
};
|
package/dist/cli/index.js
CHANGED
|
@@ -24,7 +24,7 @@ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__ge
|
|
|
24
24
|
));
|
|
25
25
|
|
|
26
26
|
// src/cli/index.ts
|
|
27
|
-
var
|
|
27
|
+
var import_commander8 = require("commander");
|
|
28
28
|
|
|
29
29
|
// src/cli/commands/board.ts
|
|
30
30
|
var import_commander = require("commander");
|
|
@@ -280,6 +280,22 @@ async function healthCheck(config) {
|
|
|
280
280
|
return false;
|
|
281
281
|
}
|
|
282
282
|
}
|
|
283
|
+
async function getPlayerStats(config, playerAddress, month) {
|
|
284
|
+
const currentMonth = month || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
285
|
+
const leaderboard = await getLeaderboard(config, currentMonth);
|
|
286
|
+
const entry = leaderboard.entries.find((e) => e.player === playerAddress);
|
|
287
|
+
if (!entry) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
const totalGames = entry.wins + entry.losses + entry.draws;
|
|
291
|
+
const winRate = totalGames > 0 ? entry.wins / totalGames * 100 : 0;
|
|
292
|
+
return {
|
|
293
|
+
...entry,
|
|
294
|
+
totalGames,
|
|
295
|
+
winRate: Math.round(winRate * 100) / 100
|
|
296
|
+
// Round to 2 decimal places
|
|
297
|
+
};
|
|
298
|
+
}
|
|
283
299
|
|
|
284
300
|
// src/client.ts
|
|
285
301
|
var ClarityXOClient = class {
|
|
@@ -329,14 +345,53 @@ var ClarityXOClient = class {
|
|
|
329
345
|
healthCheck() {
|
|
330
346
|
return healthCheck(this.config);
|
|
331
347
|
}
|
|
348
|
+
getPlayerStats(playerAddress, month) {
|
|
349
|
+
return getPlayerStats(this.config, playerAddress, month);
|
|
350
|
+
}
|
|
332
351
|
};
|
|
333
352
|
function createClient(config) {
|
|
334
353
|
return new ClarityXOClient(config);
|
|
335
354
|
}
|
|
336
355
|
|
|
356
|
+
// src/utils/validation.ts
|
|
357
|
+
function isValidStacksAddress(address) {
|
|
358
|
+
return /^S[TP][0-9A-Z]{38}$/.test(address);
|
|
359
|
+
}
|
|
360
|
+
function validateContractAddress(address) {
|
|
361
|
+
if (!address) {
|
|
362
|
+
throw new Error("Contract address is required");
|
|
363
|
+
}
|
|
364
|
+
if (!isValidStacksAddress(address)) {
|
|
365
|
+
throw new Error("Invalid Stacks address format");
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
function validateNetwork(network) {
|
|
369
|
+
if (network !== "mainnet" && network !== "testnet") {
|
|
370
|
+
throw new Error('Network must be either "mainnet" or "testnet"');
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
function validateWriteConfig(key, address) {
|
|
374
|
+
if (!key) {
|
|
375
|
+
throw new Error("--key (sender private key) is required for write operations");
|
|
376
|
+
}
|
|
377
|
+
if (!address) {
|
|
378
|
+
throw new Error("--address (sender address) is required for write operations");
|
|
379
|
+
}
|
|
380
|
+
if (!isValidStacksAddress(address)) {
|
|
381
|
+
throw new Error("Invalid sender address format");
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
|
|
337
385
|
// src/cli/commands/board.ts
|
|
338
386
|
var command = new import_commander.Command("board");
|
|
339
387
|
command.description("Display the current game board").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
388
|
+
try {
|
|
389
|
+
validateNetwork(options.network);
|
|
390
|
+
validateContractAddress(options.contract);
|
|
391
|
+
} catch (error) {
|
|
392
|
+
console.error(import_chalk.default.red(`Error: ${error.message}`));
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
340
395
|
const config = {
|
|
341
396
|
network: options.network,
|
|
342
397
|
contractAddress: options.contract,
|
|
@@ -384,6 +439,13 @@ var import_chalk2 = __toESM(require("chalk"));
|
|
|
384
439
|
var import_ora2 = __toESM(require("ora"));
|
|
385
440
|
var command2 = new import_commander2.Command("status");
|
|
386
441
|
command2.description("Display the current game status and turn").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
442
|
+
try {
|
|
443
|
+
validateNetwork(options.network);
|
|
444
|
+
validateContractAddress(options.contract);
|
|
445
|
+
} catch (error) {
|
|
446
|
+
console.error(import_chalk2.default.red(`Error: ${error.message}`));
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
387
449
|
const config = {
|
|
388
450
|
network: options.network,
|
|
389
451
|
contractAddress: options.contract,
|
|
@@ -413,6 +475,13 @@ var import_chalk3 = __toESM(require("chalk"));
|
|
|
413
475
|
var import_ora3 = __toESM(require("ora"));
|
|
414
476
|
var command3 = new import_commander3.Command("winner");
|
|
415
477
|
command3.description("Display the game winner").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
478
|
+
try {
|
|
479
|
+
validateNetwork(options.network);
|
|
480
|
+
validateContractAddress(options.contract);
|
|
481
|
+
} catch (error) {
|
|
482
|
+
console.error(import_chalk3.default.red(`Error: ${error.message}`));
|
|
483
|
+
process.exit(1);
|
|
484
|
+
}
|
|
416
485
|
const config = {
|
|
417
486
|
network: options.network,
|
|
418
487
|
contractAddress: options.contract,
|
|
@@ -443,6 +512,12 @@ var import_chalk4 = __toESM(require("chalk"));
|
|
|
443
512
|
var import_ora4 = __toESM(require("ora"));
|
|
444
513
|
var command4 = new import_commander4.Command("leaderboard");
|
|
445
514
|
command4.description("Display the leaderboard for a given month").option("--month <yyyy-mm>", "Month (YYYY-MM)", (/* @__PURE__ */ new Date()).toISOString().slice(0, 7)).option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
515
|
+
try {
|
|
516
|
+
validateNetwork(options.network);
|
|
517
|
+
} catch (error) {
|
|
518
|
+
console.error(import_chalk4.default.red(`Error: ${error.message}`));
|
|
519
|
+
process.exit(1);
|
|
520
|
+
}
|
|
446
521
|
const config = {
|
|
447
522
|
network: options.network,
|
|
448
523
|
contractAddress: options.contract,
|
|
@@ -470,13 +545,123 @@ command4.description("Display the leaderboard for a given month").option("--mont
|
|
|
470
545
|
});
|
|
471
546
|
var leaderboard_default = command4;
|
|
472
547
|
|
|
548
|
+
// src/cli/commands/move.ts
|
|
549
|
+
var import_commander5 = require("commander");
|
|
550
|
+
var import_chalk5 = __toESM(require("chalk"));
|
|
551
|
+
var import_ora5 = __toESM(require("ora"));
|
|
552
|
+
var command5 = new import_commander5.Command("move");
|
|
553
|
+
command5.description("Make a move on the board").argument("<row>", "Row (0-2)").argument("<col>", "Column (0-2)").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (row, col, options) => {
|
|
554
|
+
try {
|
|
555
|
+
validateNetwork(options.network);
|
|
556
|
+
const rowNum2 = parseInt(row, 10);
|
|
557
|
+
const colNum2 = parseInt(col, 10);
|
|
558
|
+
if (isNaN(rowNum2) || isNaN(colNum2) || rowNum2 < 0 || rowNum2 > 2 || colNum2 < 0 || colNum2 > 2) {
|
|
559
|
+
throw new Error("Row and column must be integers between 0 and 2");
|
|
560
|
+
}
|
|
561
|
+
validateWriteConfig(options.key, options.address);
|
|
562
|
+
} catch (error) {
|
|
563
|
+
console.error(import_chalk5.default.red(`Error: ${error.message}`));
|
|
564
|
+
process.exit(1);
|
|
565
|
+
}
|
|
566
|
+
const config = {
|
|
567
|
+
network: options.network,
|
|
568
|
+
contractAddress: options.contract,
|
|
569
|
+
leaderboardApiUrl: options.api,
|
|
570
|
+
senderKey: options.key,
|
|
571
|
+
senderAddress: options.address
|
|
572
|
+
};
|
|
573
|
+
const spinner = (0, import_ora5.default)("Making move...").start();
|
|
574
|
+
try {
|
|
575
|
+
const client = createClient(config);
|
|
576
|
+
const tx = await client.makeMove(rowNum, colNum);
|
|
577
|
+
spinner.stop();
|
|
578
|
+
console.log(import_chalk5.default.green(`Move successful! Transaction ID: ${tx.txId}`));
|
|
579
|
+
} catch (error) {
|
|
580
|
+
spinner.stop();
|
|
581
|
+
console.error(import_chalk5.default.red(`Error: ${error.message}`));
|
|
582
|
+
process.exit(1);
|
|
583
|
+
}
|
|
584
|
+
});
|
|
585
|
+
var move_default = command5;
|
|
586
|
+
|
|
587
|
+
// src/cli/commands/start.ts
|
|
588
|
+
var import_commander6 = require("commander");
|
|
589
|
+
var import_chalk6 = __toESM(require("chalk"));
|
|
590
|
+
var import_ora6 = __toESM(require("ora"));
|
|
591
|
+
var command6 = new import_commander6.Command("start");
|
|
592
|
+
command6.description("Start a new game").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (options) => {
|
|
593
|
+
try {
|
|
594
|
+
validateNetwork(options.network);
|
|
595
|
+
validateWriteConfig(options.key, options.address);
|
|
596
|
+
} catch (error) {
|
|
597
|
+
console.error(import_chalk6.default.red(`Error: ${error.message}`));
|
|
598
|
+
process.exit(1);
|
|
599
|
+
}
|
|
600
|
+
const config = {
|
|
601
|
+
network: options.network,
|
|
602
|
+
contractAddress: options.contract,
|
|
603
|
+
leaderboardApiUrl: options.api,
|
|
604
|
+
senderKey: options.key,
|
|
605
|
+
senderAddress: options.address
|
|
606
|
+
};
|
|
607
|
+
const spinner = (0, import_ora6.default)("Starting new game...").start();
|
|
608
|
+
try {
|
|
609
|
+
const client = createClient(config);
|
|
610
|
+
const tx = await client.startNewGame();
|
|
611
|
+
spinner.stop();
|
|
612
|
+
console.log(import_chalk6.default.green(`New game started! Transaction ID: ${tx.txId}`));
|
|
613
|
+
} catch (error) {
|
|
614
|
+
spinner.stop();
|
|
615
|
+
console.error(import_chalk6.default.red(`Error: ${error.message}`));
|
|
616
|
+
process.exit(1);
|
|
617
|
+
}
|
|
618
|
+
});
|
|
619
|
+
var start_default = command6;
|
|
620
|
+
|
|
621
|
+
// src/cli/commands/resign.ts
|
|
622
|
+
var import_commander7 = require("commander");
|
|
623
|
+
var import_chalk7 = __toESM(require("chalk"));
|
|
624
|
+
var import_ora7 = __toESM(require("ora"));
|
|
625
|
+
var command7 = new import_commander7.Command("resign");
|
|
626
|
+
command7.description("Resign the current game").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (options) => {
|
|
627
|
+
try {
|
|
628
|
+
validateNetwork(options.network);
|
|
629
|
+
validateWriteConfig(options.key, options.address);
|
|
630
|
+
} catch (error) {
|
|
631
|
+
console.error(import_chalk7.default.red(`Error: ${error.message}`));
|
|
632
|
+
process.exit(1);
|
|
633
|
+
}
|
|
634
|
+
const config = {
|
|
635
|
+
network: options.network,
|
|
636
|
+
contractAddress: options.contract,
|
|
637
|
+
leaderboardApiUrl: options.api,
|
|
638
|
+
senderKey: options.key,
|
|
639
|
+
senderAddress: options.address
|
|
640
|
+
};
|
|
641
|
+
const spinner = (0, import_ora7.default)("Resigning game...").start();
|
|
642
|
+
try {
|
|
643
|
+
const client = createClient(config);
|
|
644
|
+
const tx = await client.resignGame();
|
|
645
|
+
spinner.stop();
|
|
646
|
+
console.log(import_chalk7.default.green(`Game resigned! Transaction ID: ${tx.txId}`));
|
|
647
|
+
} catch (error) {
|
|
648
|
+
spinner.stop();
|
|
649
|
+
console.error(import_chalk7.default.red(`Error: ${error.message}`));
|
|
650
|
+
process.exit(1);
|
|
651
|
+
}
|
|
652
|
+
});
|
|
653
|
+
var resign_default = command7;
|
|
654
|
+
|
|
473
655
|
// src/cli/index.ts
|
|
474
|
-
var packageJson = { version: "0.
|
|
475
|
-
var program = new
|
|
656
|
+
var packageJson = { version: "0.2.0" };
|
|
657
|
+
var program = new import_commander8.Command();
|
|
476
658
|
program.name("clarityxo").description("CLI for ClarityXO Tic-Tac-Toe on Stacks blockchain").version(packageJson.version);
|
|
477
659
|
program.option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL");
|
|
478
660
|
program.addCommand(board_default);
|
|
479
661
|
program.addCommand(status_default);
|
|
480
662
|
program.addCommand(winner_default);
|
|
481
663
|
program.addCommand(leaderboard_default);
|
|
664
|
+
program.addCommand(move_default);
|
|
665
|
+
program.addCommand(start_default);
|
|
666
|
+
program.addCommand(resign_default);
|
|
482
667
|
program.parse();
|
package/dist/cli/index.mjs
CHANGED
|
@@ -1,17 +1,55 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
createClient
|
|
4
|
-
} from "../chunk-
|
|
4
|
+
} from "../chunk-ETEDBK3G.mjs";
|
|
5
5
|
|
|
6
6
|
// src/cli/index.ts
|
|
7
|
-
import { Command as
|
|
7
|
+
import { Command as Command8 } from "commander";
|
|
8
8
|
|
|
9
9
|
// src/cli/commands/board.ts
|
|
10
10
|
import { Command } from "commander";
|
|
11
11
|
import chalk from "chalk";
|
|
12
12
|
import ora from "ora";
|
|
13
|
+
|
|
14
|
+
// src/utils/validation.ts
|
|
15
|
+
function isValidStacksAddress(address) {
|
|
16
|
+
return /^S[TP][0-9A-Z]{38}$/.test(address);
|
|
17
|
+
}
|
|
18
|
+
function validateContractAddress(address) {
|
|
19
|
+
if (!address) {
|
|
20
|
+
throw new Error("Contract address is required");
|
|
21
|
+
}
|
|
22
|
+
if (!isValidStacksAddress(address)) {
|
|
23
|
+
throw new Error("Invalid Stacks address format");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
function validateNetwork(network) {
|
|
27
|
+
if (network !== "mainnet" && network !== "testnet") {
|
|
28
|
+
throw new Error('Network must be either "mainnet" or "testnet"');
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
function validateWriteConfig(key, address) {
|
|
32
|
+
if (!key) {
|
|
33
|
+
throw new Error("--key (sender private key) is required for write operations");
|
|
34
|
+
}
|
|
35
|
+
if (!address) {
|
|
36
|
+
throw new Error("--address (sender address) is required for write operations");
|
|
37
|
+
}
|
|
38
|
+
if (!isValidStacksAddress(address)) {
|
|
39
|
+
throw new Error("Invalid sender address format");
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// src/cli/commands/board.ts
|
|
13
44
|
var command = new Command("board");
|
|
14
45
|
command.description("Display the current game board").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
46
|
+
try {
|
|
47
|
+
validateNetwork(options.network);
|
|
48
|
+
validateContractAddress(options.contract);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error(chalk.red(`Error: ${error.message}`));
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
15
53
|
const config = {
|
|
16
54
|
network: options.network,
|
|
17
55
|
contractAddress: options.contract,
|
|
@@ -59,6 +97,13 @@ import chalk2 from "chalk";
|
|
|
59
97
|
import ora2 from "ora";
|
|
60
98
|
var command2 = new Command2("status");
|
|
61
99
|
command2.description("Display the current game status and turn").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
100
|
+
try {
|
|
101
|
+
validateNetwork(options.network);
|
|
102
|
+
validateContractAddress(options.contract);
|
|
103
|
+
} catch (error) {
|
|
104
|
+
console.error(chalk2.red(`Error: ${error.message}`));
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
62
107
|
const config = {
|
|
63
108
|
network: options.network,
|
|
64
109
|
contractAddress: options.contract,
|
|
@@ -88,6 +133,13 @@ import chalk3 from "chalk";
|
|
|
88
133
|
import ora3 from "ora";
|
|
89
134
|
var command3 = new Command3("winner");
|
|
90
135
|
command3.description("Display the game winner").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
136
|
+
try {
|
|
137
|
+
validateNetwork(options.network);
|
|
138
|
+
validateContractAddress(options.contract);
|
|
139
|
+
} catch (error) {
|
|
140
|
+
console.error(chalk3.red(`Error: ${error.message}`));
|
|
141
|
+
process.exit(1);
|
|
142
|
+
}
|
|
91
143
|
const config = {
|
|
92
144
|
network: options.network,
|
|
93
145
|
contractAddress: options.contract,
|
|
@@ -118,6 +170,12 @@ import chalk4 from "chalk";
|
|
|
118
170
|
import ora4 from "ora";
|
|
119
171
|
var command4 = new Command4("leaderboard");
|
|
120
172
|
command4.description("Display the leaderboard for a given month").option("--month <yyyy-mm>", "Month (YYYY-MM)", (/* @__PURE__ */ new Date()).toISOString().slice(0, 7)).option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").action(async (options) => {
|
|
173
|
+
try {
|
|
174
|
+
validateNetwork(options.network);
|
|
175
|
+
} catch (error) {
|
|
176
|
+
console.error(chalk4.red(`Error: ${error.message}`));
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
121
179
|
const config = {
|
|
122
180
|
network: options.network,
|
|
123
181
|
contractAddress: options.contract,
|
|
@@ -145,13 +203,123 @@ command4.description("Display the leaderboard for a given month").option("--mont
|
|
|
145
203
|
});
|
|
146
204
|
var leaderboard_default = command4;
|
|
147
205
|
|
|
206
|
+
// src/cli/commands/move.ts
|
|
207
|
+
import { Command as Command5 } from "commander";
|
|
208
|
+
import chalk5 from "chalk";
|
|
209
|
+
import ora5 from "ora";
|
|
210
|
+
var command5 = new Command5("move");
|
|
211
|
+
command5.description("Make a move on the board").argument("<row>", "Row (0-2)").argument("<col>", "Column (0-2)").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (row, col, options) => {
|
|
212
|
+
try {
|
|
213
|
+
validateNetwork(options.network);
|
|
214
|
+
const rowNum2 = parseInt(row, 10);
|
|
215
|
+
const colNum2 = parseInt(col, 10);
|
|
216
|
+
if (isNaN(rowNum2) || isNaN(colNum2) || rowNum2 < 0 || rowNum2 > 2 || colNum2 < 0 || colNum2 > 2) {
|
|
217
|
+
throw new Error("Row and column must be integers between 0 and 2");
|
|
218
|
+
}
|
|
219
|
+
validateWriteConfig(options.key, options.address);
|
|
220
|
+
} catch (error) {
|
|
221
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
222
|
+
process.exit(1);
|
|
223
|
+
}
|
|
224
|
+
const config = {
|
|
225
|
+
network: options.network,
|
|
226
|
+
contractAddress: options.contract,
|
|
227
|
+
leaderboardApiUrl: options.api,
|
|
228
|
+
senderKey: options.key,
|
|
229
|
+
senderAddress: options.address
|
|
230
|
+
};
|
|
231
|
+
const spinner = ora5("Making move...").start();
|
|
232
|
+
try {
|
|
233
|
+
const client = createClient(config);
|
|
234
|
+
const tx = await client.makeMove(rowNum, colNum);
|
|
235
|
+
spinner.stop();
|
|
236
|
+
console.log(chalk5.green(`Move successful! Transaction ID: ${tx.txId}`));
|
|
237
|
+
} catch (error) {
|
|
238
|
+
spinner.stop();
|
|
239
|
+
console.error(chalk5.red(`Error: ${error.message}`));
|
|
240
|
+
process.exit(1);
|
|
241
|
+
}
|
|
242
|
+
});
|
|
243
|
+
var move_default = command5;
|
|
244
|
+
|
|
245
|
+
// src/cli/commands/start.ts
|
|
246
|
+
import { Command as Command6 } from "commander";
|
|
247
|
+
import chalk6 from "chalk";
|
|
248
|
+
import ora6 from "ora";
|
|
249
|
+
var command6 = new Command6("start");
|
|
250
|
+
command6.description("Start a new game").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (options) => {
|
|
251
|
+
try {
|
|
252
|
+
validateNetwork(options.network);
|
|
253
|
+
validateWriteConfig(options.key, options.address);
|
|
254
|
+
} catch (error) {
|
|
255
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
256
|
+
process.exit(1);
|
|
257
|
+
}
|
|
258
|
+
const config = {
|
|
259
|
+
network: options.network,
|
|
260
|
+
contractAddress: options.contract,
|
|
261
|
+
leaderboardApiUrl: options.api,
|
|
262
|
+
senderKey: options.key,
|
|
263
|
+
senderAddress: options.address
|
|
264
|
+
};
|
|
265
|
+
const spinner = ora6("Starting new game...").start();
|
|
266
|
+
try {
|
|
267
|
+
const client = createClient(config);
|
|
268
|
+
const tx = await client.startNewGame();
|
|
269
|
+
spinner.stop();
|
|
270
|
+
console.log(chalk6.green(`New game started! Transaction ID: ${tx.txId}`));
|
|
271
|
+
} catch (error) {
|
|
272
|
+
spinner.stop();
|
|
273
|
+
console.error(chalk6.red(`Error: ${error.message}`));
|
|
274
|
+
process.exit(1);
|
|
275
|
+
}
|
|
276
|
+
});
|
|
277
|
+
var start_default = command6;
|
|
278
|
+
|
|
279
|
+
// src/cli/commands/resign.ts
|
|
280
|
+
import { Command as Command7 } from "commander";
|
|
281
|
+
import chalk7 from "chalk";
|
|
282
|
+
import ora7 from "ora";
|
|
283
|
+
var command7 = new Command7("resign");
|
|
284
|
+
command7.description("Resign the current game").option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL").option("--key <key>", "Sender private key (required)").option("--address <address>", "Sender address (required)").action(async (options) => {
|
|
285
|
+
try {
|
|
286
|
+
validateNetwork(options.network);
|
|
287
|
+
validateWriteConfig(options.key, options.address);
|
|
288
|
+
} catch (error) {
|
|
289
|
+
console.error(chalk7.red(`Error: ${error.message}`));
|
|
290
|
+
process.exit(1);
|
|
291
|
+
}
|
|
292
|
+
const config = {
|
|
293
|
+
network: options.network,
|
|
294
|
+
contractAddress: options.contract,
|
|
295
|
+
leaderboardApiUrl: options.api,
|
|
296
|
+
senderKey: options.key,
|
|
297
|
+
senderAddress: options.address
|
|
298
|
+
};
|
|
299
|
+
const spinner = ora7("Resigning game...").start();
|
|
300
|
+
try {
|
|
301
|
+
const client = createClient(config);
|
|
302
|
+
const tx = await client.resignGame();
|
|
303
|
+
spinner.stop();
|
|
304
|
+
console.log(chalk7.green(`Game resigned! Transaction ID: ${tx.txId}`));
|
|
305
|
+
} catch (error) {
|
|
306
|
+
spinner.stop();
|
|
307
|
+
console.error(chalk7.red(`Error: ${error.message}`));
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
var resign_default = command7;
|
|
312
|
+
|
|
148
313
|
// src/cli/index.ts
|
|
149
|
-
var packageJson = { version: "0.
|
|
150
|
-
var program = new
|
|
314
|
+
var packageJson = { version: "0.2.0" };
|
|
315
|
+
var program = new Command8();
|
|
151
316
|
program.name("clarityxo").description("CLI for ClarityXO Tic-Tac-Toe on Stacks blockchain").version(packageJson.version);
|
|
152
317
|
program.option("--contract [address]", "Contract address").option("--network <network>", "Network (mainnet or testnet)", "testnet").option("--api <url>", "Leaderboard API URL");
|
|
153
318
|
program.addCommand(board_default);
|
|
154
319
|
program.addCommand(status_default);
|
|
155
320
|
program.addCommand(winner_default);
|
|
156
321
|
program.addCommand(leaderboard_default);
|
|
322
|
+
program.addCommand(move_default);
|
|
323
|
+
program.addCommand(start_default);
|
|
324
|
+
program.addCommand(resign_default);
|
|
157
325
|
program.parse();
|
package/dist/index.d.mts
CHANGED
|
@@ -33,6 +33,16 @@ interface LeaderboardEntry {
|
|
|
33
33
|
points: number;
|
|
34
34
|
rank: number;
|
|
35
35
|
}
|
|
36
|
+
interface PlayerStats {
|
|
37
|
+
player: string;
|
|
38
|
+
wins: number;
|
|
39
|
+
losses: number;
|
|
40
|
+
draws: number;
|
|
41
|
+
points: number;
|
|
42
|
+
rank: number;
|
|
43
|
+
totalGames: number;
|
|
44
|
+
winRate: number;
|
|
45
|
+
}
|
|
36
46
|
interface LeaderboardMonth {
|
|
37
47
|
month: string;
|
|
38
48
|
entries: LeaderboardEntry[];
|
|
@@ -73,6 +83,7 @@ declare class ClarityXOClient {
|
|
|
73
83
|
submitResult(result: GameResult): Promise<void>;
|
|
74
84
|
syncLeaderboard(): Promise<void>;
|
|
75
85
|
healthCheck(): Promise<boolean>;
|
|
86
|
+
getPlayerStats(playerAddress: string, month?: string): Promise<PlayerStats | null>;
|
|
76
87
|
}
|
|
77
88
|
declare function createClient(config: ClarityXOConfig): ClarityXOClient;
|
|
78
89
|
|
|
@@ -113,5 +124,14 @@ declare function getLeaderboard(config: ClarityXOConfig, month: string): Promise
|
|
|
113
124
|
declare function submitResult(config: ClarityXOConfig, result: GameResult): Promise<void>;
|
|
114
125
|
declare function syncLeaderboard(config: ClarityXOConfig): Promise<void>;
|
|
115
126
|
declare function healthCheck(config: ClarityXOConfig): Promise<boolean>;
|
|
127
|
+
declare function getPlayerStats(config: ClarityXOConfig, playerAddress: string, month?: string): Promise<PlayerStats | null>;
|
|
128
|
+
|
|
129
|
+
declare function checkWin(board: Board, player: CellValue): boolean;
|
|
130
|
+
declare function isBoardFull(board: Board): boolean;
|
|
131
|
+
declare function isDraw(board: Board): boolean;
|
|
132
|
+
declare function getAvailableMoves(board: Board): Array<[number, number]>;
|
|
133
|
+
declare function getBoardString(board: Board): string;
|
|
134
|
+
declare function cloneBoard(board: Board): Board;
|
|
135
|
+
declare function makeMoveOnBoard(board: Board, row: number, col: number, player: CellValue): Board;
|
|
116
136
|
|
|
117
|
-
export { type Board, CONTRACT_ADDRESS, CONTRACT_FUNCTIONS, CONTRACT_NAME, type CellValue, ClarityXOClient, type ClarityXOConfig, DEFAULT_LEADERBOARD_API, type GameResult, type GameState, type GameStatus, type LeaderboardEntry, type LeaderboardMonth, MAINNET_API, type Network, TESTNET_API, type Turn, createClient, getBoardState, getCurrentTurn, getFullGameState, getGameStatus, getLeaderboard, getWinner, healthCheck, isValidMove, makeMove, resignGame, startNewGame, submitResult, syncLeaderboard };
|
|
137
|
+
export { type Board, CONTRACT_ADDRESS, CONTRACT_FUNCTIONS, CONTRACT_NAME, type CellValue, ClarityXOClient, type ClarityXOConfig, DEFAULT_LEADERBOARD_API, type GameResult, type GameState, type GameStatus, type LeaderboardEntry, type LeaderboardMonth, MAINNET_API, type Network, type PlayerStats, TESTNET_API, type Turn, checkWin, cloneBoard, createClient, getAvailableMoves, getBoardState, getBoardString, getCurrentTurn, getFullGameState, getGameStatus, getLeaderboard, getPlayerStats, getWinner, healthCheck, isBoardFull, isDraw, isValidMove, makeMove, makeMoveOnBoard, resignGame, startNewGame, submitResult, syncLeaderboard };
|
package/dist/index.d.ts
CHANGED
|
@@ -33,6 +33,16 @@ interface LeaderboardEntry {
|
|
|
33
33
|
points: number;
|
|
34
34
|
rank: number;
|
|
35
35
|
}
|
|
36
|
+
interface PlayerStats {
|
|
37
|
+
player: string;
|
|
38
|
+
wins: number;
|
|
39
|
+
losses: number;
|
|
40
|
+
draws: number;
|
|
41
|
+
points: number;
|
|
42
|
+
rank: number;
|
|
43
|
+
totalGames: number;
|
|
44
|
+
winRate: number;
|
|
45
|
+
}
|
|
36
46
|
interface LeaderboardMonth {
|
|
37
47
|
month: string;
|
|
38
48
|
entries: LeaderboardEntry[];
|
|
@@ -73,6 +83,7 @@ declare class ClarityXOClient {
|
|
|
73
83
|
submitResult(result: GameResult): Promise<void>;
|
|
74
84
|
syncLeaderboard(): Promise<void>;
|
|
75
85
|
healthCheck(): Promise<boolean>;
|
|
86
|
+
getPlayerStats(playerAddress: string, month?: string): Promise<PlayerStats | null>;
|
|
76
87
|
}
|
|
77
88
|
declare function createClient(config: ClarityXOConfig): ClarityXOClient;
|
|
78
89
|
|
|
@@ -113,5 +124,14 @@ declare function getLeaderboard(config: ClarityXOConfig, month: string): Promise
|
|
|
113
124
|
declare function submitResult(config: ClarityXOConfig, result: GameResult): Promise<void>;
|
|
114
125
|
declare function syncLeaderboard(config: ClarityXOConfig): Promise<void>;
|
|
115
126
|
declare function healthCheck(config: ClarityXOConfig): Promise<boolean>;
|
|
127
|
+
declare function getPlayerStats(config: ClarityXOConfig, playerAddress: string, month?: string): Promise<PlayerStats | null>;
|
|
128
|
+
|
|
129
|
+
declare function checkWin(board: Board, player: CellValue): boolean;
|
|
130
|
+
declare function isBoardFull(board: Board): boolean;
|
|
131
|
+
declare function isDraw(board: Board): boolean;
|
|
132
|
+
declare function getAvailableMoves(board: Board): Array<[number, number]>;
|
|
133
|
+
declare function getBoardString(board: Board): string;
|
|
134
|
+
declare function cloneBoard(board: Board): Board;
|
|
135
|
+
declare function makeMoveOnBoard(board: Board, row: number, col: number, player: CellValue): Board;
|
|
116
136
|
|
|
117
|
-
export { type Board, CONTRACT_ADDRESS, CONTRACT_FUNCTIONS, CONTRACT_NAME, type CellValue, ClarityXOClient, type ClarityXOConfig, DEFAULT_LEADERBOARD_API, type GameResult, type GameState, type GameStatus, type LeaderboardEntry, type LeaderboardMonth, MAINNET_API, type Network, TESTNET_API, type Turn, createClient, getBoardState, getCurrentTurn, getFullGameState, getGameStatus, getLeaderboard, getWinner, healthCheck, isValidMove, makeMove, resignGame, startNewGame, submitResult, syncLeaderboard };
|
|
137
|
+
export { type Board, CONTRACT_ADDRESS, CONTRACT_FUNCTIONS, CONTRACT_NAME, type CellValue, ClarityXOClient, type ClarityXOConfig, DEFAULT_LEADERBOARD_API, type GameResult, type GameState, type GameStatus, type LeaderboardEntry, type LeaderboardMonth, MAINNET_API, type Network, type PlayerStats, TESTNET_API, type Turn, checkWin, cloneBoard, createClient, getAvailableMoves, getBoardState, getBoardString, getCurrentTurn, getFullGameState, getGameStatus, getLeaderboard, getPlayerStats, getWinner, healthCheck, isBoardFull, isDraw, isValidMove, makeMove, makeMoveOnBoard, resignGame, startNewGame, submitResult, syncLeaderboard };
|
package/dist/index.js
CHANGED
|
@@ -27,16 +27,24 @@ __export(index_exports, {
|
|
|
27
27
|
DEFAULT_LEADERBOARD_API: () => DEFAULT_LEADERBOARD_API,
|
|
28
28
|
MAINNET_API: () => MAINNET_API,
|
|
29
29
|
TESTNET_API: () => TESTNET_API,
|
|
30
|
+
checkWin: () => checkWin,
|
|
31
|
+
cloneBoard: () => cloneBoard,
|
|
30
32
|
createClient: () => createClient,
|
|
33
|
+
getAvailableMoves: () => getAvailableMoves,
|
|
31
34
|
getBoardState: () => getBoardState,
|
|
35
|
+
getBoardString: () => getBoardString,
|
|
32
36
|
getCurrentTurn: () => getCurrentTurn,
|
|
33
37
|
getFullGameState: () => getFullGameState,
|
|
34
38
|
getGameStatus: () => getGameStatus,
|
|
35
39
|
getLeaderboard: () => getLeaderboard,
|
|
40
|
+
getPlayerStats: () => getPlayerStats,
|
|
36
41
|
getWinner: () => getWinner,
|
|
37
42
|
healthCheck: () => healthCheck,
|
|
43
|
+
isBoardFull: () => isBoardFull,
|
|
44
|
+
isDraw: () => isDraw,
|
|
38
45
|
isValidMove: () => isValidMove,
|
|
39
46
|
makeMove: () => makeMove,
|
|
47
|
+
makeMoveOnBoard: () => makeMoveOnBoard,
|
|
40
48
|
resignGame: () => resignGame,
|
|
41
49
|
startNewGame: () => startNewGame,
|
|
42
50
|
submitResult: () => submitResult,
|
|
@@ -295,6 +303,22 @@ async function healthCheck(config) {
|
|
|
295
303
|
return false;
|
|
296
304
|
}
|
|
297
305
|
}
|
|
306
|
+
async function getPlayerStats(config, playerAddress, month) {
|
|
307
|
+
const currentMonth = month || (/* @__PURE__ */ new Date()).toISOString().slice(0, 7);
|
|
308
|
+
const leaderboard = await getLeaderboard(config, currentMonth);
|
|
309
|
+
const entry = leaderboard.entries.find((e) => e.player === playerAddress);
|
|
310
|
+
if (!entry) {
|
|
311
|
+
return null;
|
|
312
|
+
}
|
|
313
|
+
const totalGames = entry.wins + entry.losses + entry.draws;
|
|
314
|
+
const winRate = totalGames > 0 ? entry.wins / totalGames * 100 : 0;
|
|
315
|
+
return {
|
|
316
|
+
...entry,
|
|
317
|
+
totalGames,
|
|
318
|
+
winRate: Math.round(winRate * 100) / 100
|
|
319
|
+
// Round to 2 decimal places
|
|
320
|
+
};
|
|
321
|
+
}
|
|
298
322
|
|
|
299
323
|
// src/client.ts
|
|
300
324
|
var ClarityXOClient = class {
|
|
@@ -344,10 +368,66 @@ var ClarityXOClient = class {
|
|
|
344
368
|
healthCheck() {
|
|
345
369
|
return healthCheck(this.config);
|
|
346
370
|
}
|
|
371
|
+
getPlayerStats(playerAddress, month) {
|
|
372
|
+
return getPlayerStats(this.config, playerAddress, month);
|
|
373
|
+
}
|
|
347
374
|
};
|
|
348
375
|
function createClient(config) {
|
|
349
376
|
return new ClarityXOClient(config);
|
|
350
377
|
}
|
|
378
|
+
|
|
379
|
+
// src/utils/board.ts
|
|
380
|
+
function checkWin(board, player) {
|
|
381
|
+
if (player === null) return false;
|
|
382
|
+
for (let row = 0; row < 3; row++) {
|
|
383
|
+
if (board[row][0] === player && board[row][1] === player && board[row][2] === player) {
|
|
384
|
+
return true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
for (let col = 0; col < 3; col++) {
|
|
388
|
+
if (board[0][col] === player && board[1][col] === player && board[2][col] === player) {
|
|
389
|
+
return true;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (board[0][0] === player && board[1][1] === player && board[2][2] === player) {
|
|
393
|
+
return true;
|
|
394
|
+
}
|
|
395
|
+
if (board[0][2] === player && board[1][1] === player && board[2][0] === player) {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
return false;
|
|
399
|
+
}
|
|
400
|
+
function isBoardFull(board) {
|
|
401
|
+
return board.every((row) => row.every((cell) => cell !== null));
|
|
402
|
+
}
|
|
403
|
+
function isDraw(board) {
|
|
404
|
+
return isBoardFull(board) && !checkWin(board, "X") && !checkWin(board, "O");
|
|
405
|
+
}
|
|
406
|
+
function getAvailableMoves(board) {
|
|
407
|
+
const moves = [];
|
|
408
|
+
for (let row = 0; row < 3; row++) {
|
|
409
|
+
for (let col = 0; col < 3; col++) {
|
|
410
|
+
if (board[row][col] === null) {
|
|
411
|
+
moves.push([row, col]);
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
return moves;
|
|
416
|
+
}
|
|
417
|
+
function getBoardString(board) {
|
|
418
|
+
return board.map((row) => row.map((cell) => cell || " ").join("")).join("\n");
|
|
419
|
+
}
|
|
420
|
+
function cloneBoard(board) {
|
|
421
|
+
return board.map((row) => [...row]);
|
|
422
|
+
}
|
|
423
|
+
function makeMoveOnBoard(board, row, col, player) {
|
|
424
|
+
if (board[row][col] !== null) {
|
|
425
|
+
throw new Error("Cell is already occupied");
|
|
426
|
+
}
|
|
427
|
+
const newBoard = cloneBoard(board);
|
|
428
|
+
newBoard[row][col] = player;
|
|
429
|
+
return newBoard;
|
|
430
|
+
}
|
|
351
431
|
// Annotate the CommonJS export names for ESM import in node:
|
|
352
432
|
0 && (module.exports = {
|
|
353
433
|
CONTRACT_ADDRESS,
|
|
@@ -357,16 +437,24 @@ function createClient(config) {
|
|
|
357
437
|
DEFAULT_LEADERBOARD_API,
|
|
358
438
|
MAINNET_API,
|
|
359
439
|
TESTNET_API,
|
|
440
|
+
checkWin,
|
|
441
|
+
cloneBoard,
|
|
360
442
|
createClient,
|
|
443
|
+
getAvailableMoves,
|
|
361
444
|
getBoardState,
|
|
445
|
+
getBoardString,
|
|
362
446
|
getCurrentTurn,
|
|
363
447
|
getFullGameState,
|
|
364
448
|
getGameStatus,
|
|
365
449
|
getLeaderboard,
|
|
450
|
+
getPlayerStats,
|
|
366
451
|
getWinner,
|
|
367
452
|
healthCheck,
|
|
453
|
+
isBoardFull,
|
|
454
|
+
isDraw,
|
|
368
455
|
isValidMove,
|
|
369
456
|
makeMove,
|
|
457
|
+
makeMoveOnBoard,
|
|
370
458
|
resignGame,
|
|
371
459
|
startNewGame,
|
|
372
460
|
submitResult,
|
package/dist/index.mjs
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
getFullGameState,
|
|
13
13
|
getGameStatus,
|
|
14
14
|
getLeaderboard,
|
|
15
|
+
getPlayerStats,
|
|
15
16
|
getWinner,
|
|
16
17
|
healthCheck,
|
|
17
18
|
isValidMove,
|
|
@@ -20,7 +21,60 @@ import {
|
|
|
20
21
|
startNewGame,
|
|
21
22
|
submitResult,
|
|
22
23
|
syncLeaderboard
|
|
23
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-ETEDBK3G.mjs";
|
|
25
|
+
|
|
26
|
+
// src/utils/board.ts
|
|
27
|
+
function checkWin(board, player) {
|
|
28
|
+
if (player === null) return false;
|
|
29
|
+
for (let row = 0; row < 3; row++) {
|
|
30
|
+
if (board[row][0] === player && board[row][1] === player && board[row][2] === player) {
|
|
31
|
+
return true;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (let col = 0; col < 3; col++) {
|
|
35
|
+
if (board[0][col] === player && board[1][col] === player && board[2][col] === player) {
|
|
36
|
+
return true;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
if (board[0][0] === player && board[1][1] === player && board[2][2] === player) {
|
|
40
|
+
return true;
|
|
41
|
+
}
|
|
42
|
+
if (board[0][2] === player && board[1][1] === player && board[2][0] === player) {
|
|
43
|
+
return true;
|
|
44
|
+
}
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
function isBoardFull(board) {
|
|
48
|
+
return board.every((row) => row.every((cell) => cell !== null));
|
|
49
|
+
}
|
|
50
|
+
function isDraw(board) {
|
|
51
|
+
return isBoardFull(board) && !checkWin(board, "X") && !checkWin(board, "O");
|
|
52
|
+
}
|
|
53
|
+
function getAvailableMoves(board) {
|
|
54
|
+
const moves = [];
|
|
55
|
+
for (let row = 0; row < 3; row++) {
|
|
56
|
+
for (let col = 0; col < 3; col++) {
|
|
57
|
+
if (board[row][col] === null) {
|
|
58
|
+
moves.push([row, col]);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return moves;
|
|
63
|
+
}
|
|
64
|
+
function getBoardString(board) {
|
|
65
|
+
return board.map((row) => row.map((cell) => cell || " ").join("")).join("\n");
|
|
66
|
+
}
|
|
67
|
+
function cloneBoard(board) {
|
|
68
|
+
return board.map((row) => [...row]);
|
|
69
|
+
}
|
|
70
|
+
function makeMoveOnBoard(board, row, col, player) {
|
|
71
|
+
if (board[row][col] !== null) {
|
|
72
|
+
throw new Error("Cell is already occupied");
|
|
73
|
+
}
|
|
74
|
+
const newBoard = cloneBoard(board);
|
|
75
|
+
newBoard[row][col] = player;
|
|
76
|
+
return newBoard;
|
|
77
|
+
}
|
|
24
78
|
export {
|
|
25
79
|
CONTRACT_ADDRESS,
|
|
26
80
|
CONTRACT_FUNCTIONS,
|
|
@@ -29,16 +83,24 @@ export {
|
|
|
29
83
|
DEFAULT_LEADERBOARD_API,
|
|
30
84
|
MAINNET_API,
|
|
31
85
|
TESTNET_API,
|
|
86
|
+
checkWin,
|
|
87
|
+
cloneBoard,
|
|
32
88
|
createClient,
|
|
89
|
+
getAvailableMoves,
|
|
33
90
|
getBoardState,
|
|
91
|
+
getBoardString,
|
|
34
92
|
getCurrentTurn,
|
|
35
93
|
getFullGameState,
|
|
36
94
|
getGameStatus,
|
|
37
95
|
getLeaderboard,
|
|
96
|
+
getPlayerStats,
|
|
38
97
|
getWinner,
|
|
39
98
|
healthCheck,
|
|
99
|
+
isBoardFull,
|
|
100
|
+
isDraw,
|
|
40
101
|
isValidMove,
|
|
41
102
|
makeMove,
|
|
103
|
+
makeMoveOnBoard,
|
|
42
104
|
resignGame,
|
|
43
105
|
startNewGame,
|
|
44
106
|
submitResult,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "clarityxo-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "TypeScript SDK and CLI for the ClarityXO on-chain Tic-Tac-Toe game on Stacks",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -31,7 +31,6 @@
|
|
|
31
31
|
"web3"
|
|
32
32
|
],
|
|
33
33
|
"author": "Dominion <limbotech116@gmail.com>",
|
|
34
|
-
"author": "Dominion <limbotech116@gmail.com>",
|
|
35
34
|
"license": "MIT",
|
|
36
35
|
"repository": {
|
|
37
36
|
"type": "git",
|