èæ¯
- ATrium ã¨ãã AT Protocol ã®ããã®ã©ã¤ãã©ãªãèªä½ãã¦ãã
- ããã¾ã£ããããã°ãã¼ãã£ã³ã°ãã¦ããªãã£ã
- ã®ã§ãBlueskyã«è©°å°æ£ã®åé¡ãæ¾æµããBotãä½ã£ã¦ã¿ããã¨ã«ãã
- gfxæ°ãä½ã£ãBot ãåèã«
- ã¨ããããã§ãè©°å°æ£ã®åé¡ã®å±é¢ãç»åã§æ稿ããã
- ãããã¾ãèªå好ã¿ã®ç»åãçæã§ããã©ã¤ãã©ãªãWebãµã¼ãã¹çããªã
- ã®ã§ãçµå±ãããèªåã§ä½ããã¨ã«ãã
å è¡ã»é¡ä¼¼äºä¾
- Shogipic
- https://shogipic.jp/
- ããªããã¬ã¤ãªå±é¢å³ç»åãçæã§ãã
- ãµã¼ãã§çæããæ¹å¼ã§APIãå ¬éããã¦ããããã§ã¯ãªã
- ã¯ã©ã¦ãå°æ£å±é¢å³ã¸ã§ãã¬ã¼ã¿ã¼
- https://sfenreader2.appspot.com/
- Python on Google App Engineã§åãã¦ããããã
- SFENå½¢å¼ã®Queryããç»åãçæããAPIããã
- æªãã¯ãªããShogipicã®æ¹ã好ã¿
- å°æ£å±é¢å³ã®ç»åä½æ(SVG,PNG)
- https://shogi.zukeran.org/shogi-draw/
- ãã©ã¦ã¶ä¸ã®JSã§çæããå½¢å¼ï¼
- cshogi
- https://tadaoyamaoka.hatenablog.com/entry/2019/08/17/163308
- Python libraryããç»åãçæã§ãã
- åºåã¯SVGå½¢å¼
ãã®ä»ãèªåã§ãéå»ã«Goã§ä½æãããã®ããã£ãããããä»ã¯ã¡ã³ããã¦ããªãã¦åããªããç»åç´ æãå ¬éãã¦ãã ãã£ã¦ãããµã¤ããæ¶æ» ãã¦ãã¾ã£ã¦ããã
èªä½ã®ã¡ãªãã
- èªå好ã¿ã®ç»åãçæã§ãã
- Rustã®ããã°ã©ã ãã使ããã©ã¤ãã©ãªã¨ãã¦åå¨ãã¦ããã¨å¬ãã
- WASMåãã¦Webã¢ããªåããããããããããªã
Rustã§å±é¢ç»åçæ
åºæ¬çã«ã¯ãééPNGã®ç´ æãçµã¿åããã¦ç¤ä¸ã«é§ãé ç½®ããã ãã
ãã¨ã¯æã¡é§ã®è¡¨ç¾ãæ§ã ãªè¡¨ç¤ºæ¹æ³ãããããæ©ã¯æ大18æãªã®ã§éãã¦ä¸¦ã¹ãã®ã¯å¾®å¦ã§ãç´ ç´ã«åæ°ãæ°åã¨ãã¦ã¬ã³ããªã³ã°ããæ¹ãåãããããã¨æãã
ç¤ã»é§ç»åã®ç´ æ
æè¿ã§ã¯ Electronå°æ£ ã®Kubo, Ryosukeããã使ããããå½¢ã§ç´ æç»åãå ¬éãã¦ãã ãã£ã¦ããã
ã®ã§ãããã使ããã¦ããã ããã¨ã«ããã äºæåé§ãããã¨å¬ããã£ãããä¸ææ追å ããã¦ãããã®ã®è«¸äºæ ã§åé¤ããã¦ãã¾ã£ãããã ã ä»å¾ 誰ãããªãªã¸ãã«ã§ä½æãã¦ããã«è¿½å ãã¦ããããããããã«ãªãã¨ãããªâ¦ã
ç»åå¦ç
image ã¨ããã©ã¤ãã©ãªãç»åå¦çã«ãã使ããã¦ããããã ã PNG以å¤ã«ãæ§ã ãªç»åå½¢å¼ã«å¯¾å¿ãã¦ãã¦ãæ¡å¤§ç¸®å°ãéãåãããªã©ã®åºæ¬çãªå¦çãç°¡åã«ã§ããã
ä½ææã«ã¯PNGç»åãåæããæä½ã ããªã®ã§ããã£ã¨è»½ãã©ã¤ãã©ãªã§åæ§ã®ãã®ãå®ç¾ã§ãããªããã¡ãã®æ¹ãè¯ããããããªããã¨ããããã¯image
ã§å®è£
ãã¦ã¿ãã
å ¥åºå
å
¥åã¯å±é¢ã®æ
å ±ãæã¤ãã®ã¨ã㦠shogi_core ã® PartialPosition
ã使ããã¨ã«ãããåºåã¯image
ã®RgbaImage
ã
ã©ã¤ãã©ãªä½¿ç¨è
ã¯å¿
è¦ã«å¿ãã¦ä¾ãã° shogi_usi_parse ã使ã£ã¦SFENæååããPartialPosition
ãä½ãããããå
ã«çæããçµæã®RgbaImage
ãå å·¥ããã好ããªå½¢å¼ã§ãã¡ã¤ã«ã«æ¸ãåºãããããã°è¯ããã¨ããæ³å®ã
pub fn pos2img(position: &shogi_core::PartialPosition) -> image::RgbaImage { Generator::default().generate(position) }
Generatorã¨ä¸æºå
å±é¢ç»åãä½æããéã®è¨ç®è² è·ãæ¸ããããGeenrator
ã¨ããæ§é ä½ãç¨æããã
ã¹ã¿ã¤ã«ãæå®ãã¦new
ããæç¹ã§å¿
è¦ãªé§ãªã©ã®ç´ æãã¼ã¿ãèªã¿è¾¼ãã§ãããgenerate
ãå¼ã°ããéã«ã¯ããããè²¼ãåãããã ããã«ããã
ããã«ãã£ã¦ãåãã¹ã¿ã¤ã«ã§è¤æ°ã®å±é¢ãçæããå ´åã«ã¯ãç´ æã®èªã¿è¾¼ã¿ãä¸åº¦ã§æ¸ãã§é«éã«çæã§ããããã«ãªããã¯ãã
ç¤ã¨é§ã®ç»åã¯äºåã«ãµã¤ãºã決ãã¦ãããããããåãåºããä¸ã§æé©åããPNGãã¡ã¤ã«ã¨ãã¦ç½®ãã¦ããããã¡ã¤ã«èªã¿è¾¼ã¿ã¯ãããinclude_bytes!
ã§ã¡ã¢ãªä¸ããèªã¿è¾¼ãã§ä½¿ãã
Publish
ã¨ããããã§åºæ¥ä¸ãã£ãã®ã§ crates.io ã«å ¬éããã ãèªç±ã«ã使ããã ããã
Web Appã§ä½¿ã
ãã£ããã©ã¤ãã©ãªã¨ãã¦ä½ã£ãã®ã§ãã¡ãã£ã¨å å·¥ãã¦Web Appã¨ãã¦ã使ããã¨å¬ãããããããªããã¨æã£ã¦ä½ã£ã¦ã¿ããã¨ã«ããã
å±é¢ã表ç¾ããSFENæåå㯠lnsgkgsnl/1r5b1/ppppppppp/9/9/9/PPPPPPPPP/1B5R1/LNSGKGSNL b - 1
ã®ããã«è¡¨ç¾ãããããããURLã®Pathã¨ãã¦åãåã£ã¦ã対å¿ããå±é¢ç»åãè¿ããããªãã®ãèããã
ä¾:
https://example.com/3sks3/9/4S4/9/1+B7/9/9/9/9%20b%20S2rb4g4n4l18p%201
ã¹ãã¼ã¹ã¯ %20
ã«ã¨ã³ã³ã¼ãããå¿
è¦ãããã®ã§ã¡ãã£ã¨ã«ãã³æªããâ¦ã
CDN Edgeã§åãã
æè¿ããèãããã«ãªã£ãEdge Computingãã¾ã 試ãããã¨ããªãã£ãã®ã§ããã®æ©ä¼ã«è²ã 触ã£ã¦ã¿ããã¨ã«ããã
wasm-packã§WebAssemblyä½æ
ã¾ãã¯ç°¡åãªã¤ã³ã¿ãã§ã¼ã¹ã決ãã¦ãWebAssemblyã§ä½¿ããããã«ããã±ã¼ã¸ãç¨æããã
ç°¡åã«æ±ããããã«ãå
¥åã¯SFENæååã¨ãshogi_usi_parse
ã§ãããparseãå±é¢æ
å ±ãåå¾ãåºåã¯çæããç»åã®PNGå½¢å¼ãã¤ããªãã¼ã¿ã¨ããã
use shogi_img::{image, pos2img, shogi_core}; use shogi_usi_parser::FromUsi; use std::io::Cursor; use wasm_bindgen::prelude::*; #[wasm_bindgen] pub fn sfen2png(sfen: &str) -> Vec<u8> { let pos = shogi_core::PartialPosition::from_usi(sfen).unwrap_or_default(); let mut cursor = Cursor::new(Vec::new()); pos2img(&pos) .write_to(&mut cursor, image::ImageFormat::Png) .ok(); cursor.into_inner() }
ãã¨ã¯ wasm-pack
ã§ãã«ãããã°ããæãã«wasmãã¡ã¤ã«ãçæãããã
Deno Deploy
ä¸è¨ã®wasmã使ã£ã¦æãç°¡åã«å®ç¾ã§ããã®ããDeno Deploy ã ã£ãã
wasm-pack build --target deno
ããã¨Denoç¨ã®wasmãã¡ã¤ã«ãçæããããã¨ã¯ãããèªã¿è¾¼ãã§ä½¿ãã ãã
import { sfen2png } from "./pkg/sfen2png.js"; Deno.serve((req: Request) => { const sfen = `sfen ${decodeURI(new URL(req.url).pathname).slice(1)}`; return new Response(sfen2png(sfen), { headers: { "content-type": "image/png", }, }); });
ããã ãã®ã³ã¼ããæ¸ãã¦ããã¨ã¯deployããã°åããç°¡åããã¦ãããã
$ curl -I https://sfen2img.deno.dev/ HTTP/2 200 content-type: image/png vary: Accept-Encoding date: Wed, 24 Jan 2024 14:39:32 GMT content-length: 447458 via: http/2 edgeproxy-h server: deno/gcp-asia-northeast1
Vercel Edge Functions
åæ§ã«wasmãèªã¿è¾¼ãã§ä½¿ãããã®ã¨ãã¦ãVercel Edge Functions ã試ãã¦ã¿ãã
ããã¥ã¡ã³ãã«ã¯ wasm-pack
ã使ã£ãä¾ãç¡ãããã§ãä»ã®äººãä½ã£ã¦ããexample repositoryãªã©ãåèã«ãã¦ã©ãã«ãã
wasm-pack build --target web
ã§çæããNext.jsã§/src/app/[[...slug]]/route.ts
ã§ä½¿ãã
// @ts-ignore import wasm from "@/pkg/sfen2png_bg.wasm?module"; import init, { sfen2png } from "@/pkg/sfen2png.js"; export const runtime = "edge"; export const dynamic = "force-dynamic"; export async function GET(request: Request) { await init(wasm); const sfen = `sfen ${decodeURI(new URL(request.url).pathname).slice(1)}`; return new Response(sfen2png(sfen), { headers: { "content-type": "image/png", }, }); }
ã¨ããããããã§ãã¼ã«ã«ã§ã¯åãããdeployãã¦ã¿ããã¨ããã
Error: The Edge Function "[[...slug]]" size is 1.62 MB and your plan size limit is 1 MB.
ã¨ãªã£ã¦ãã¾ã£ããwasmã ãã§1.3MBããããããHobbyãã©ã³ã§ã¯1MBã¾ã§ ã¨ããå¶éãè¶ ãã¦ãã¾ãããã ã Pro以ä¸ã®ãã©ã³ã«ãããããã£ã¨è»½ããã¤ããªã«ãªãããã«å·¥å¤«ãå¿ è¦ã«ãªãâ¦ã
Cloudflare Workers
ããååãèãã®ã§æ¯é試ãã¦ã¿ããã£ããCloudflare Workersã
wrangler
ã§ããã¸ã§ã¯ããä½æããéã« worker-rust
ã®ãã³ãã¬ã¼ãã使ãã¨ãRust㧠worker
ã使ãRustããã¸ã§ã¯ããçæãããã
wrangler generate [name] https://github.com/cloudflare/workers-sdk/templates/experimental/worker-rust
ãã㦠lib.rs
ã以ä¸ã®ããã«ç·¨éããã ãã
use sfen2png_wasm::sfen2png; use urlencoding::decode; use worker::*; #[event(fetch)] async fn main(req: Request, _env: Env, _ctx: Context) -> Result<Response> { let decoded = decode(&req.path()) .map(String::from) .expect("decode failed"); let sfen = format!("sfen {}", decoded.strip_prefix('/').expect("invalid path")); Ok(Response::from_bytes(sfen2png(&sfen))? .with_headers(Headers::from_iter([("content-type", "image/png")]))) }
JavaScriptã®decodeURI
åçã®ãã®ã¯æ¨æºã«ã¯ç¡ããããªã®ã§ urlencoding
ã©ã¤ãã©ãªã使ã£ã¦ããã
ãã¨ã¯ wrangler deploy
ããã ãããã®ä¸ã§ worker-build
ã«ãã£ã¦Cloudflare Workersç¨ã«è«¸ã
ãã«ããããããã§ãworker-build
ãå
é¨ã§wasm-pack
ãå®è¡ããããã¦ããããã ã
Rustã ãã§å®çµããJS/TSã®ã³ã¼ããæ¸ãå¿
è¦ããªããwrangler
ã®ããã«npm
ã使ããããã
Cloudflare WorkersãVercel Edge Functionsã¨åæ§ã« Free planã§ã¯ 1 MB ã¾ã§ ã¨ããå¶éããããããã¡ã㯠size after compression ã¨ãããã¨ã§äºåã«gzipå§ç¸®ãã¦éä¿¡ãã¦ããããããã§660KBç¨åº¦ã«ãªããå¶éã«å¼ã£ããããã¨ãªãdeployæåããã
Total Upload: 1414.62 KiB / gzip: 659.91 KiB
$ curl -I https://sfen2img.sugyan.workers.dev/ HTTP/2 200 date: Thu, 25 Jan 2024 06:16:53 GMT content-type: image/png content-length: 447458 report-to: {"endpoints":[{"url":"https:\/\/a.nel.cloudflare.com\/report\/v3?s=cT5XwLyJxYgiWXTJ85RHOGIRSxlpJSnl%2F6iPDoQW097set8BelY7M%2Fc6mRULGktwSqBU2Jlv6UoJ3w6eYFDM4bBjNHmZPGvfrnipB8u4Fxmkc3T%2BjFVUyF80dEFkxsn5X1Lp9OzTRhytCWAf%2BLA%3D"}],"group":"cf-nel","max_age":604800} nel: {"success_fraction":0,"report_to":"cf-nel","max_age":604800} server: cloudflare cf-ray: 84ae63d57d908370-KIX alt-svc: h3=":443"; ma=86400
Fastly Compute@Edge
æå¾ã« Cloudflare Workersã¨ä¸¦ãã§ããååãèããFastly Compute@Edgeã
ãã¡ã㯠fastly
ã¨ããCLIãã¼ã«ã使ãã
$ fastly compute init ... Language: (Find out more about language support at https://developer.fastly.com/learning/compute) [1] Rust [2] JavaScript [3] Go [4] Other ('bring your own' Wasm binary)
ãã®ãããããé¸ã¹ãããä»åã¯Rustã§ãCloudflare Workersã¨åæ§ã«Rustããã¸ã§ã¯ããçæãããã®ã§ãåãããã«main.rs
ãç·¨éãã¦ããã
use fastly::http::header; use fastly::{Error, Request, Response}; use sfen2png_wasm::sfen2png; use urlencoding::decode; #[fastly::main] fn main(req: Request) -> Result<Response, Error> { let decoded = decode(req.get_path())?; let sfen = format!("sfen {}", decoded.strip_prefix('/').expect("invalid path")); Ok(Response::from_body(sfen2png(&sfen)).with_header(header::CONTENT_TYPE, "image/png")) }
Request/Responseãªã©ã®ã¤ã³ã¿ãã§ã¼ã¹ã¯ worker
ã¨ã¯å¤å°ç°ãªãããããã¾ãã«ã¯åããªã®ã§ãããããã®å
容ã§ããã°ã»ã¼åããããªæè¦ã§æ¸ããã
ãã¨ã¯ fastly compute publish
ããã ãããã¡ã㯠wasm-pack
ãªã©ã使ããªãã
$ curl -I https://sfen2img.edgecompute.app/ HTTP/2 200 content-type: image/png x-served-by: cache-nrt-rjtf7700062-NRT date: Thu, 25 Jan 2024 07:26:11 GMT
ãã®ä»
Edge Computingç³»ã®ãµã¼ãã¹ã¯ä»ã«ãå¤ãããããã ããä»åã¯ãããããã§ã 大æµã¯JSããwasmãå¼ã³åºãå½¢ã§Vercel Edge Functionsã¨åæ§ã«åããããã¨äºæ³ãã¦ããã
ãµã¤ãºå¶éã¯å³ããå ´åãå¤ãã®ã§ã.wasmã1MBãåãç¨åº¦ã«ã¯è»½éã§ããæ¹ãæã¾ãããã§ã¯ããâ¦ã
ã¾ã¨ã
å±é¢ç»åçæã©ã¤ãã©ãªãä½ã£ããããã§ãè©°å°æ£ç»åãæ稿ããBluesky BotãRustã ãã§ä½ããã¨ãã§ããã
ã¹ã¯ã¬ã¤ãã³ã°ãã¦å¾ãkifãã¡ã¤ã«ãparseãã¦å±é¢æ å ±ãåå¾ããé¨åã èªä½ã©ã¤ãã©ãª ã使ã£ã¦ããã®ã§ã
- kifãã
PartialPosition
ã¸ã®å¤æ PartialPosition
ããç»åçæ- çæç»åãå«ãPostãBlueskyã«æ稿
ã®3ã¤ãèªä½ã®ã©ã¤ãã©ãªã使ã£ã¦å®ç¾ãã¦ãããã¨ã«ãªãã
å¯ç£ç©ã¨ãã¦ãEdgeã§åççæããWeb Appãè¤æ°ã§ããã ç¡ææ ã§ãã®ã¾ã¾ç½®ãã¦ããã®ã§ã使ããéãã¯ãèªç±ã«ã使ããã ããã