Skip to content

Commit

Permalink
feat: add chess-generate-image tool
Browse files Browse the repository at this point in the history
  • Loading branch information
guillevalin committed Feb 4, 2025
1 parent 941d0c3 commit f354830
Show file tree
Hide file tree
Showing 6 changed files with 181 additions and 0 deletions.
Binary file added tools/chess-generate-image/banner.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added tools/chess-generate-image/icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
70 changes: 70 additions & 0 deletions tools/chess-generate-image/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { expect } from '@jest/globals';
import { getToolTestClient } from '../../src/test/utils';
import * as path from 'path';
import * as fs from 'fs';

describe('Chess Generate Image Tool', () => {
const toolPath = path.join(__dirname, 'tool.py');
const client = getToolTestClient();

it('generates an image for the starting position', async () => {
const response = await client.executeToolFromFile(toolPath, {
fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
output_filename: "test_starting_position.png"
});

console.log(response);

expect(response).toHaveProperty('image_path');
expect(typeof response.image_path).toBe('string');
expect(response.image_path).toContain('test_starting_position.png');
expect(fs.existsSync(response.image_path)).toBe(true);

// Check if file is a valid PNG
const fileBuffer = fs.readFileSync(response.image_path);
expect(fileBuffer.length).toBeGreaterThan(0);
expect(fileBuffer.toString('hex', 0, 8)).toBe('89504e470d0a1a0a'); // PNG magic number
}, 20000);

it('generates an image with highlighted last move', async () => {
const response = await client.executeToolFromFile(toolPath, {
fen: "rnbqkbnr/pppppppp/8/8/4P3/8/PPPP1PPP/RNBQKBNR b KQkq e3 0 1",
last_move_uci: "e2e4",
output_filename: "test_with_highlight.png"
});

console.log(response);

expect(response).toHaveProperty('image_path');
expect(typeof response.image_path).toBe('string');
expect(response.image_path).toContain('test_with_highlight.png');
expect(fs.existsSync(response.image_path)).toBe(true);

const fileBuffer = fs.readFileSync(response.image_path);
expect(fileBuffer.length).toBeGreaterThan(0);
expect(fileBuffer.toString('hex', 0, 8)).toBe('89504e470d0a1a0a');
}, 20000);

it('handles invalid FEN', async () => {
await expect(client.executeToolFromFile(toolPath, {
fen: "not-a-valid-FEN"
})).rejects.toThrow(/Invalid FEN/);
}, 10000);

it('handles invalid last_move_uci', async () => {
const response = await client.executeToolFromFile(toolPath, {
fen: "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1",
last_move_uci: "invalid-move",
output_filename: "test_invalid_move.png"
});

expect(response).toHaveProperty('image_path');
expect(typeof response.image_path).toBe('string');
expect(response.image_path).toContain('test_invalid_move.png');
expect(fs.existsSync(response.image_path)).toBe(true);

const fileBuffer = fs.readFileSync(response.image_path);
expect(fileBuffer.length).toBeGreaterThan(0);
expect(fileBuffer.toString('hex', 0, 8)).toBe('89504e470d0a1a0a');
}, 20000);
});
49 changes: 49 additions & 0 deletions tools/chess-generate-image/metadata.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
{
"id": "chess-generate-image",
"name": "Chess Generate Image",
"version": "1.0.0",
"description": "Generate a PNG image of a chess position from FEN notation with optional last move highlighting",
"author": "Example",
"keywords": [
"chess",
"image",
"png",
"fen",
"board",
"visualization"
],
"configurations": {
"type": "object",
"properties": {},
"required": []
},
"parameters": {
"type": "object",
"properties": {
"fen": {
"type": "string",
"description": "FEN string representing the chess position"
},
"last_move_uci": {
"type": "string",
"description": "Optional UCI format move to highlight (e.g. 'e2e4')"
},
"output_filename": {
"type": "string",
"description": "Optional filename for the output PNG image (default: chess_position.png)",
"default": "chess_position.png"
}
},
"required": ["fen"]
},
"result": {
"type": "object",
"properties": {
"image_path": {
"type": "string",
"description": "Path to the generated PNG image file"
}
},
"required": ["image_path"]
}
}
3 changes: 3 additions & 0 deletions tools/chess-generate-image/store.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
"categoryId": "f4906ba5-16bb-445d-9241-85422cf5a055"
}
59 changes: 59 additions & 0 deletions tools/chess-generate-image/tool.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# /// script
# dependencies = [
# "python-chess>=1.999",
# "cairosvg>=2.5.2",
# "requests"
# ]
# ///
import chess
import chess.svg
import cairosvg
import os
from typing import Dict, Any, Optional, List
from shinkai_local_support import get_home_path

class CONFIG:
pass

class INPUTS:
fen: str
last_move_uci: Optional[str] = None # highlight the last move, optional
output_filename: str = "chess_position.png" # allow customizing the output filename

class OUTPUT:
image_path: str

async def run(c: CONFIG, p: INPUTS) -> OUTPUT:
if not p.fen:
raise ValueError("No FEN provided")

board = chess.Board()
try:
board.set_fen(p.fen)
except ValueError:
raise ValueError("Invalid FEN")

# Optionally highlight the last move
arrows = []
if p.last_move_uci and len(p.last_move_uci) in (4,5):
try:
move = board.parse_uci(p.last_move_uci)
arrows.append(chess.svg.Arrow(start=move.from_square, end=move.to_square, color="#FF0000"))
except:
pass

svg_data = chess.svg.board(board, arrows=arrows)
png_data = cairosvg.svg2png(bytestring=svg_data.encode("utf-8"))

# Get home path and create output path
home_path = await get_home_path()
filename = p.output_filename
file_path = os.path.join(home_path, filename)

# Write the PNG data to file
with open(file_path, "wb") as f:
f.write(png_data)

out = OUTPUT()
out.image_path = file_path
return out

0 comments on commit f354830

Please sign in to comment.