Skip to content

Commit

Permalink
feat: Add termination circles on the last frame of the game
Browse files Browse the repository at this point in the history
Indicate the reason why the game ended by drawing a small circle on top of each king.
The winning king gets a crown, the loser king gets a symbol indicating the reason why
it lost (timeout, resignation, or checkmate), and in the event of a draw a 1/2 is drawn
on both kings.

This required moving all svgs under a single directory svgs/.

Finally, also updated the README with more examples.
  • Loading branch information
tomasfarias committed May 30, 2021
1 parent e048d04 commit caaa952
Show file tree
Hide file tree
Showing 34 changed files with 603 additions and 107 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 4 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
[package]
name = "c2g"
version = "0.5.7"
version = "0.6.0"
authors = ["Tomas Farias <[email protected]>"]
edition = "2018"

[features]
default = ["include-fonts", "include-pieces"]
default = ["include-fonts", "include-svgs"]
# Embeds the fonts directory at compile time. Removes the need to pass a path to a local font when running the CLI.
include-fonts = ["include_dir"]

# Embeds the pieces directory at compile time. Removes the need to pass a path to a local directory containing SVG pieces when running the CLI.
include-pieces = ["include_dir"]
# Embeds the svgs directory at compile time. Removes the need to pass a path to a local directory containing SVG pieces and terminations when running the CLI.
include-svgs = ["include_dir"]

[dependencies]
clap = "2.33"
Expand Down
39 changes: 36 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ Turn your chess PGNs to GIFs!

## Examples

Running:

### Basic
```
$ cat example/example.pgn | ./c2g --size 640 --output example/chess.gif
```
Expand All @@ -14,6 +13,40 @@ Will output the following 640x640 GIF:

![Example](/example/chess.gif)

### Bullet

Bullet games are good candidates for real time delay:
```
$ cat example/example_bullet.pgn | ./c2g --size 640 --delay="real" --output example/chess_bullet.gif
```

![Example-Bullet](/example/chess_bullet.gif)

### Clean

If you prefer a more cleaner look without any [features](#Features), you can disable them:
```
$ cat example/example_no_clock.pgn | ./c2g --size 640 --no-player-bars --no-terminations --output example/chess_clean.gif
```

![Example-Clean](/example/chess_clean.gif)

## Features

### Player bars

At the top and bottom of the gif we include the player's username and elo (if available). This feature can be disabled by passing `--no-player-bars`.

### Clocks and real time

If the chess PGN contains `%clk` comments, c2g will attempt to parse them into durations to try and estimate the time taken per move. The clock for each turn is included in the [player bars](#Player bars). Moreover, if the duration is available, we can ask c2g to use the real duration as the delay between gif frames, with the `--delay="real"`option. This is particularly exciting for bullet games that usually last 1-2 minutes or less.

### Termination circles

The last frame of the gif will draw a small circle over each king to show the result of the game. Some terminations have special circles to indicate the reason why the game ended. Since there are many possible reasons to terminate a chess game, we make use of the Termination PGN header to try to narrow down the cause. If the header is not available, or we cannot find any reason in it, we make the assumption that the losing side resigned for the purpose of chossing what circle to draw. For now, all possible draws are treated the same for the purpose of which circle will be drawn.

This feature can be disabled with `--no-termination`

## License

Any file in this project that is not listed as an exception is licensed under the GNU General Public License 3.
Expand All @@ -22,5 +55,5 @@ The following (free) exceptions apply:

| Files | Author(s) | License |
| :-- | :-- | :-- |
| pieces/cburnett/*.svg | [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt) |
| svgs/pieces/cburnett/*.svg | [Colin M.L. Burnett](https://en.wikipedia.org/wiki/User:Cburnett) | [GPLv2+](https://www.gnu.org/licenses/gpl-2.0.txt) |
| fonts/roboto.ttf | [Christian Robertson](https://fonts.google.com/specimen/Roboto) | [Apache 2.0](https://www.apache.org/licenses/LICENSE-2.0) |
Binary file modified example/chess.gif
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 example/chess_bullet.gif
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 example/chess_clean.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
17 changes: 17 additions & 0 deletions example/example_bullet.pgn
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[Event "Live Chess"]
[Site "Chess.com"]
[Date "2021.05.17"]
[White "penguingm1"]
[Black "DanielNaroditsky"]
[Result "0-1"]
[CurrentPosition "rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1"]
[ECO "B23"]
[WhiteElo "3316"]
[BlackElo "3353"]
[TimeControl "30"]
[EndTime "0:49:14 PDT"]
[Termination "DanielNaroditsky won by checkmate"]
[Link "https://www.chess.com/game/live/14976478223"]

1. e4 {[%clk 0:00:30.0]} 1... c5 {[%clk 0:00:30.0]} 2. Nc3 {[%clk 0:00:29.7]} 2... d6 {[%clk 0:00:29.7]} 3. g3 {[%clk 0:00:29.4]} 3... Nc6 {[%clk 0:00:29.5]} 4. Bg2 {[%clk 0:00:29.3]} 4... g6 {[%clk 0:00:29.2]} 5. Nge2 {[%clk 0:00:29.2]} 5... Bg7 {[%clk 0:00:29.1]} 6. O-O {[%clk 0:00:28.9]} 6... e6 {[%clk 0:00:28.7]} 7. d3 {[%clk 0:00:28.6]} 7... Nge7 {[%clk 0:00:28.6]} 8. Be3 {[%clk 0:00:28.3]} 8... O-O {[%clk 0:00:28.5]} 9. Qd2 {[%clk 0:00:28.2]} 9... Nd4 {[%clk 0:00:28.4]} 10. Bh6 {[%clk 0:00:27.9]} 10... Nxe2+ {[%clk 0:00:27.6]} 11. Nxe2 {[%clk 0:00:27.8]} 11... Nc6 {[%clk 0:00:27.0]} 12. Bxg7 {[%clk 0:00:27.2]} 12... Kxg7 {[%clk 0:00:26.9]} 13. f4 {[%clk 0:00:27.1]} 13... f5 {[%clk 0:00:26.8]} 14. g4 {[%clk 0:00:26.3]} 14... fxg4 {[%clk 0:00:26.2]} 15. f5 {[%clk 0:00:26.0]} 15... exf5 {[%clk 0:00:25.7]} 16. exf5 {[%clk 0:00:25.9]} 16... Bxf5 {[%clk 0:00:25.5]} 17. Ng3 {[%clk 0:00:25.5]} 17... Nd4 {[%clk 0:00:25.0]} 18. c3 {[%clk 0:00:23.7]} 18... Nc6 {[%clk 0:00:24.6]} 19. Rf2 {[%clk 0:00:23.0]} 19... Qh4 {[%clk 0:00:24.0]} 20. Raf1 {[%clk 0:00:22.6]} 20... Be6 {[%clk 0:00:23.8]} 21. Qe3 {[%clk 0:00:22.1]} 21... Rxf2 {[%clk 0:00:23.0]} 22. Rxf2 {[%clk 0:00:22.0]} 22... Qe7 {[%clk 0:00:22.1]} 23. Qf4 {[%clk 0:00:19.5]} 23... Rf8 {[%clk 0:00:21.6]} 24. Qe3 {[%clk 0:00:19.0]} 24... Rxf2 {[%clk 0:00:21.0]} 25. Qxf2 {[%clk 0:00:18.9]} 25... Bf7 {[%clk 0:00:20.6]} 26. Ne4 {[%clk 0:00:18.3]} 26... b6 {[%clk 0:00:19.3]} 27. Qf4 {[%clk 0:00:17.7]} 27... Qe5 {[%clk 0:00:18.6]} 28. Qf2 {[%clk 0:00:17.2]} 28... d5 {[%clk 0:00:18.2]} 29. Nd2 {[%clk 0:00:16.5]} 29... Ne7 {[%clk 0:00:17.7]} 30. Nf1 {[%clk 0:00:16.1]} 30... Qf6 {[%clk 0:00:17.4]} 31. Qd2 {[%clk 0:00:16.0]} 31... Nf5 {[%clk 0:00:16.9]} 32. Ng3 {[%clk 0:00:15.0]} 32... Nxg3 {[%clk 0:00:16.4]} 33. hxg3 {[%clk 0:00:14.9]} 33... h6 {[%clk 0:00:16.1]} 34. d4 {[%clk 0:00:14.0]} 34... Qg5 {[%clk 0:00:16.0]} 35. Qxg5 {[%clk 0:00:13.0]} 35... hxg5 {[%clk 0:00:15.9]} 36. Kf2 {[%clk 0:00:12.9]} 36... Kf6 {[%clk 0:00:15.5]} 37. Ke3 {[%clk 0:00:12.6]} 37... c4 {[%clk 0:00:15.2]} 38. a3 {[%clk 0:00:12.5]} 38... Be6 {[%clk 0:00:14.8]} 39. Bf1 {[%clk 0:00:12.4]} 39... Ke7 {[%clk 0:00:14.6]} 40. Be2 {[%clk 0:00:12.3]} 40... Kd6 {[%clk 0:00:14.4]} 41. Bd1 {[%clk 0:00:12.2]} 41... Bf5 {[%clk 0:00:14.2]} 42. Bc2 {[%clk 0:00:12.1]} 42... Bxc2 {[%clk 0:00:13.5]} 43. Kd2 {[%clk 0:00:11.9]} 43... Be4 {[%clk 0:00:13.4]} 44. Ke3 {[%clk 0:00:11.8]} 44... Kc6 {[%clk 0:00:13.0]} 45. a4 {[%clk 0:00:10.5]} 45... Kb7 {[%clk 0:00:12.5]} 46. Kd2 {[%clk 0:00:10.1]} 46... Ka6 {[%clk 0:00:12.1]} 47. b4 {[%clk 0:00:10.0]} 47... cxb3 {[%clk 0:00:11.6]} 48. Kc1 {[%clk 0:00:09.8]} 48... Ka5 {[%clk 0:00:11.5]} 49. Kb2 {[%clk 0:00:09.7]} 49... Kxa4 {[%clk 0:00:11.4]} 50. c4 {[%clk 0:00:09.4]} 50... dxc4 {[%clk 0:00:10.7]} 51. d5 {[%clk 0:00:09.0]} 51... Bxd5 {[%clk 0:00:10.6]} 52. Kc3 {[%clk 0:00:08.9]} 52... Ka3 {[%clk 0:00:10.1]} 53. Kd4 {[%clk 0:00:08.5]} 53... b2 {[%clk 0:00:10.0]} 54. Ke5 {[%clk 0:00:08.4]} 54... b1=Q {[%clk 0:00:09.7]} 55. Kf6 {[%clk 0:00:08.3]} 55... Qf5+ {[%clk 0:00:09.2]} 56. Kg7 {[%clk 0:00:08.0]} 56... c3 {[%clk 0:00:08.6]} 57. Kh6 {[%clk 0:00:07.9]} 57... c2 {[%clk 0:00:08.5]} 58. Kg7 {[%clk 0:00:07.8]} 58... c1=Q {[%clk 0:00:08.2]} 59. Kh6 {[%clk 0:00:07.7]} 59... Qcc8 {[%clk 0:00:07.2]} 60. Kg7 {[%clk 0:00:07.6]} 60... Qg8+ {[%clk 0:00:06.9]} 61. Kh6 {[%clk 0:00:07.5]} 61... Qh8# {[%clk 0:00:06.6]} 0-1

19 changes: 19 additions & 0 deletions example/example_no_clock.pgn
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
[Event "Live Chess"]
[Site "Chess.com"]
[Date "2021.04.18"]
[Round "?"]
[White "slowbullet"]
[Black "ChessBrah"]
[Result "0-1"]
[ECO "B50"]
[WhiteElo "1436"]
[BlackElo "2880"]
[TimeControl "180"]
[EndTime "22:25:48 PDT"]
[Termination "ChessBrah won by checkmate"]

1. e4 c5 2. Nf3 d6 3. Bc4 Nc6 4. O-O e6 5. d4 cxd4 6. Nxd4 Nf6 7. Nc3 Be7 8. Be3
O-O 9. Bd3 Nxd4 10. Bxd4 b6 11. e5 dxe5 12. Bxe5 Bb7 13. f3 Rc8 14. Ne4 Nd5 15.
Qe1 f6 16. Bc3 Nxc3 17. Nxc3 e5 18. Bf5 Rc5 19. Rd1 Qb8 20. Be6+ Kh8 21. Qh4 Bc8
22. Bd5 Qc7 23. g4 f5 24. Qh3 b5 25. Ne4 fxe4 26. Bxe4 h6 27. Qh5 Rc4 28. Bf5
Bxf5 29. gxf5 Rh4 30. Qg6 e4 31. Qe6 Qxh2# 0-1
53 changes: 40 additions & 13 deletions src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ impl Chess2Gif {
T: Into<OsString> + Clone,
{
let app = App::new("Chess to GIF")
.version("0.5.6")
.version("0.6.0")
.author("Tomas Farias <[email protected]>")
.about("Turns a PGN chess game into a GIF")
.arg(
Expand Down Expand Up @@ -82,6 +82,12 @@ impl Chess2Gif {
.takes_value(false)
.help("Disable player bars at the top and bottom of the GIF"),
)
.arg(
Arg::with_name("no-terminations")
.long("no-terminations")
.takes_value(false)
.help("Do not draw termination circles at the end of the GIF"),
)
.arg(
Arg::with_name("dark")
.short("d")
Expand All @@ -108,15 +114,21 @@ impl Chess2Gif {
Arg::with_name("pieces-path")
.long("pieces-path")
.takes_value(true)
.help("Path to directory containing SVGs of chess pieces. If compiled with include-pieces (default), this argument can be used to set a different family of pieces, defaults to cburnett")
.default_value("cburnett"),
.required(false)
.help("Path to directory containing SVGs of chess pieces. If compiled with include-svgs (default), this argument can be used to set a different family of pieces, defaults to cburnett"),
).arg(
Arg::with_name("terminations-path")
.long("terminations-path")
.takes_value(true)
.required(false)
.help("Path to directory containing SVGs of termination circles. If compiled with include-svgs (default), this argument can be ignored"),
)
.arg(
Arg::with_name("font-path")
.long("font-path")
.takes_value(true)
.help("Path to desired coordinates font. If compiled with include-fonts, this argument can be used to set a different coordinate font, defaults to roboto")
.default_value("roboto.ttf"),
.required(false)
.help("Path to desired coordinates font. If compiled with include-svgs, this argument can be used to set a different coordinate font, defaults to roboto"),
);

let matches = app.get_matches_from_safe(args)?;
Expand All @@ -134,12 +146,24 @@ impl Chess2Gif {
None
};

let pieces_path = matches
.value_of("pieces-path")
.expect("Path to pieces must be defined");
let font_path = matches
.value_of("font-path")
.expect("Path to coordinates must be defined");
let pieces_path = match matches.value_of("pieces-path") {
Some(p) => {
if cfg!(feature = "include-svgs") {
format!("pieces/{}", p)
} else {
p.to_string()
}
}
None => "pieces/cburnett".to_string(),
};
let terminations_path = match matches.value_of("terminations-path") {
Some(p) => p,
None => "terminations",
};
let font_path = match matches.value_of("font-path") {
Some(p) => p,
None => "roboto.ttf",
};

let output = matches.value_of("output").expect("Output must be defined");

Expand Down Expand Up @@ -169,14 +193,17 @@ impl Chess2Gif {
};

let no_player_bars = matches.is_present("no-player-bars");
let no_terminations = matches.is_present("no-terminations");

Ok(Chess2Gif {
pgn: pgn,
pgn,
giffer: PGNGiffer::new(
pieces_path,
&pieces_path,
font_path,
terminations_path,
flip,
!no_player_bars,
!no_terminations,
size,
output,
delay,
Expand Down
Loading

0 comments on commit caaa952

Please sign in to comment.