Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

Merge pull request #2 from 2sb18/master

PGN generator
  • Loading branch information...
commit d890f16a4ac0174019ffe5f3d23fccae5028563d 2 parents ec1ae14 + b1efabf
@jhlywa authored
Showing with 162 additions and 1 deletion.
  1. +18 −1 README.md
  2. +81 −0 chess.js
  3. +63 −0 tests/tests.js
View
19 README.md
@@ -82,6 +82,23 @@ Returns the FEN string for the current position.
chess.fen();
// -> 'rnbqkbnr/pppp1ppp/8/4p3/4PP2/8/PPPP2PP/RNBQKBNR b KQkq f3 0 2'
+### .info()
+Allows header information to be added to PGN output. Any number of key/values
+can be passed to .info(). See .pgn() for example.
+
+### .pgn(options)
+Returns the game in PGN format. Options is an object that can include
+max width and/or a newline character.
+
+ var chess = new Chess();
+ chess.info("White", "Plunky", "Black", "Plinkie");
+ chess.move('e4');
+ chess.move('e5');
+ chess.move('Nc3');
+ chess.move('Nc6');
+
+ chess.pgn({max_width:5, newline_char:"<br />"});
+ // -> '[White "Plunky"]<br />[Black "Plinkie"]<br /><br />1. e4 e5<br />2. Nc3 Nc6'
### .ascii()
@@ -359,5 +376,5 @@ Returns the color of the square ('light' or 'dark').
## TODO
-- add method to generate PGN ([Portable Game Notation](http://en.wikipedia.org/wiki/Portable_Game_Notation)) output
+- add method to input PGN
- add AI (basic alpha-beta search w/ primitive position evaluation)
View
81 chess.js
@@ -147,6 +147,7 @@ var Chess = function(fen) {
var half_moves = 0;
var move_number = 1;
var history = [];
+ var info = {}; // holds PGN header information.
/* if the user passes in a fen string, load it, else default to
* starting position
@@ -166,6 +167,7 @@ var Chess = function(fen) {
half_moves = 0;
move_number = 1;
history = [];
+ info = {};
}
function reset() {
@@ -272,6 +274,74 @@ var Chess = function(fen) {
return [fen, turn, cflags, epflags, half_moves, move_number].join(' ')
}
+
+ // using the specification from http://www.chessclub.com/help/PGN-spec
+ // options_obj can contain the maxium width and a newline character
+ // example for html usage: options_obj = {max_width:72, newline_char:"<br />"}
+ function generate_pgn(options_obj) {
+ var result = [];
+ var move_strings = [];
+ var move_string = "";
+ var current_move_number = 1;
+ var newline_char = (typeof options_obj === "object" && typeof options_obj.newline_char === "string") ? options_obj.newline_char : "\n";
+ var max_width = (typeof options_obj === "object" && typeof options_obj.max_width === "number") ? options_obj.max_width : 0;
+ var current_width = 0;
+
+ // add the PGN header information
+ for (var i in info) {
+ result.push("[" + i + " \"" + info[i] + "\"]" + newline_char);
+ }
+ result.push(newline_char);
+
+ // pop all of history onto reversedHistory
+ var reversed_history = [];
+ while (history.length > 0) {
+ reversed_history.push(undo_move());
+ }
+
+ // fill the array of move_strings. Example of a move_string: "3. e3 e6"
+ while(reversed_history.length > 0) {
+ if (history.length % 2 === 0) {
+ move_string.length ? move_strings.push(move_string) : null;
+ move_string = current_move_number + ".";
+ current_move_number++;
+ }
+ var current_move = reversed_history.pop();
+ move_string = move_string + " " + move_to_san(current_move);
+ make_move(current_move);
+ }
+ move_string.length ? move_strings.push(move_string) : null;
+
+ // history should be back to what is was before we started generating pgn
+
+ // join together move_strings
+ if (max_width === 0) {
+ return result.join("") + move_strings.join(" ");
+ }
+
+ for (var i = 0; i < move_strings.length; i++) {
+ if (current_width + move_strings[i].length > max_width && i !== 0) {
+ result.push(newline_char);
+ current_width = 0;
+ } else if (i !== 0) {
+ result.push(" ");
+ current_width++;
+ }
+ result.push(move_strings[i]);
+ current_width += move_strings[i].length;
+ }
+ return result.join("");
+ }
+
+ // ex. set_info('White', 'Jeff Hlywa', 'Black', 'Steve Bragg')
+ function set_info(args) {
+ for (var i = 0; i < args.length; i += 2) {
+ if (typeof args[i] === "string" && typeof args[i + 1] === "string") {
+ info[args[i]] = args[i + 1];
+ }
+ }
+ return info;
+ }
function get(square) {
var piece = board[SQUARES[square]];
@@ -1051,6 +1121,16 @@ var Chess = function(fen) {
fen: function() {
return generate_fen();
},
+
+ // options_obj can contain width and a newline character
+ // example for html usage: options_obj = {max_width:72, newline_char:"<br />"}
+ pgn: function(options_obj) {
+ return generate_pgn(options_obj);
+ },
+
+ info: function() {
+ return set_info(arguments);
+ },
ascii: function() {
return ascii();
@@ -1149,3 +1229,4 @@ var Chess = function(fen) {
/* export Chess object if using node or any other CommonJS compatible
* environment */
if (typeof exports != 'undefined') exports.Chess = Chess;
+
View
63 tests/tests.js
@@ -356,6 +356,68 @@ function fen_tests() {
log('');
}
+function pgn_tests() {
+ var chess = new Chess();
+ var start = new Date;
+ var passed = true;
+ var error_message;
+ var positions = [
+ {moves: ['d4', 'd5', 'Nf3', 'Nc6', 'e3', 'e6', 'Bb5', 'g5', 'O-O', 'Qf6', 'Nc3',
+ 'Bd7', 'Bxc6', 'Bxc6', 'Re1', 'O-O-O', 'a4', 'Bb4', 'a5', 'b5', 'axb6',
+ 'axb6', 'Ra8+', 'Kd7', 'Ne5+', 'Kd6', 'Rxd8+', 'Qxd8', 'Nxf7+', 'Ke7',
+ 'Nxd5+', 'Qxd5', 'c3', 'Kxf7', 'Qf3+', 'Qxf3', 'gxf3', 'Bxf3', 'cxb4',
+ 'e5', 'dxe5', 'Ke6', 'b3', 'Kxe5', 'Bb2+', 'Ke4', 'Bxh8', 'Nf6', 'Bxf6',
+ 'h5', 'Bxg5', 'Bg2', 'Kxg2', 'Kf5', 'Bh4', 'Kg4', 'Bg3', 'Kf5', 'e4+',
+ 'Kg4', 'e5', 'h4', 'Bxh4', 'Kxh4', 'e6', 'c5', 'bxc5', 'bxc5', 'e7', 'c4',
+ 'bxc4', 'Kg4', 'e8=Q', 'Kf5', 'Qe5+', 'Kg4', 'Re4#'],
+ info: ['White', 'Jeff Hlywa', 'Black', 'Steve Bragg', 'GreatestGameEverPlayed?', 'True'],
+ max_width:19,
+ newline_char:"<br />",
+ pgn: '[White "Jeff Hlywa"]<br />[Black "Steve Bragg"]<br />[GreatestGameEverPlayed? "True"]<br /><br />1. d4 d5 2. Nf3 Nc6<br />3. e3 e6 4. Bb5 g5<br />5. O-O Qf6<br />6. Nc3 Bd7<br />7. Bxc6 Bxc6<br />8. Re1 O-O-O<br />9. a4 Bb4 10. a5 b5<br />11. axb6 axb6<br />12. Ra8+ Kd7<br />13. Ne5+ Kd6<br />14. Rxd8+ Qxd8<br />15. Nxf7+ Ke7<br />16. Nxd5+ Qxd5<br />17. c3 Kxf7<br />18. Qf3+ Qxf3<br />19. gxf3 Bxf3<br />20. cxb4 e5<br />21. dxe5 Ke6<br />22. b3 Kxe5<br />23. Bb2+ Ke4<br />24. Bxh8 Nf6<br />25. Bxf6 h5<br />26. Bxg5 Bg2<br />27. Kxg2 Kf5<br />28. Bh4 Kg4<br />29. Bg3 Kf5<br />30. e4+ Kg4<br />31. e5 h4<br />32. Bxh4 Kxh4<br />33. e6 c5<br />34. bxc5 bxc5<br />35. e7 c4<br />36. bxc4 Kg4<br />37. e8=Q Kf5<br />38. Qe5+ Kg4<br />39. Re4#',
+ fen: '8/8/8/4Q3/2P1R1k1/8/5PKP/8 b - - 4 39'},
+ {moves: ['c4', 'e6', 'Nf3', 'd5', 'd4', 'Nf6', 'Nc3', 'Be7', 'Bg5', 'O-O', 'e3', 'h6',
+ 'Bh4', 'b6', 'cxd5', 'Nxd5', 'Bxe7', 'Qxe7', 'Nxd5', 'exd5', 'Rc1', 'Be6',
+ 'Qa4', 'c5', 'Qa3', 'Rc8', 'Bb5', 'a6', 'dxc5', 'bxc5', 'O-O', 'Ra7',
+ 'Be2', 'Nd7', 'Nd4', 'Qf8', 'Nxe6', 'fxe6', 'e4', 'd4', 'f4', 'Qe7',
+ 'e5', 'Rb8', 'Bc4', 'Kh8', 'Qh3', 'Nf8', 'b3', 'a5', 'f5', 'exf5',
+ 'Rxf5', 'Nh7', 'Rcf1', 'Qd8', 'Qg3', 'Re7', 'h4', 'Rbb7', 'e6', 'Rbc7',
+ 'Qe5', 'Qe8', 'a4', 'Qd8', 'R1f2', 'Qe8', 'R2f3', 'Qd8', 'Bd3', 'Qe8',
+ 'Qe4', 'Nf6', 'Rxf6', 'gxf6', 'Rxf6', 'Kg8', 'Bc4', 'Kh8', 'Qf4'],
+ info: ["Event", "Reykjavik WCh", "Site", "Reykjavik WCh", "Date", "1972.01.07", "EventDate", "?", "Round", "6", "Result", "1-0",
+ "White", "Robert James Fischer", "Black", "Boris Spassky", "ECO", "D59", "WhiteElo", "?", "BlackElo", "?", "PlyCount", "81"],
+ max_width:65,
+ pgn: '[Event "Reykjavik WCh"]\n[Site "Reykjavik WCh"]\n[Date "1972.01.07"]\n[EventDate "?"]\n[Round "6"]\n[Result "1-0"]\n[White "Robert James Fischer"]\n[Black "Boris Spassky"]\n[ECO "D59"]\n[WhiteElo "?"]\n[BlackElo "?"]\n[PlyCount "81"]\n\n1. c4 e6 2. Nf3 d5 3. d4 Nf6 4. Nc3 Be7 5. Bg5 O-O 6. e3 h6\n7. Bh4 b6 8. cxd5 Nxd5 9. Bxe7 Qxe7 10. Nxd5 exd5 11. Rc1 Be6\n12. Qa4 c5 13. Qa3 Rc8 14. Bb5 a6 15. dxc5 bxc5 16. O-O Ra7\n17. Be2 Nd7 18. Nd4 Qf8 19. Nxe6 fxe6 20. e4 d4 21. f4 Qe7\n22. e5 Rb8 23. Bc4 Kh8 24. Qh3 Nf8 25. b3 a5 26. f5 exf5\n27. Rxf5 Nh7 28. Rcf1 Qd8 29. Qg3 Re7 30. h4 Rbb7 31. e6 Rbc7\n32. Qe5 Qe8 33. a4 Qd8 34. R1f2 Qe8 35. R2f3 Qd8 36. Bd3 Qe8\n37. Qe4 Nf6 38. Rxf6 gxf6 39. Rxf6 Kg8 40. Bc4 Kh8 41. Qf4',
+ fen: '4q2k/2r1r3/4PR1p/p1p5/P1Bp1Q1P/1P6/6P1/6K1 b - - 4 41'},
+ {moves: ['f3', 'e5', 'g4', 'Qh4#'],
+ info: ["White", "child", "Black", "Steve Bragg", "Steve'sComment", "It's not fair"],
+ max_width:1,
+ pgn: '[White "child"]\n[Black "Steve Bragg"]\n[Steve\'sComment "It\'s not fair"]\n\n1. f3 e5\n2. g4 Qh4#',
+ fen: 'rnb1kbnr/pppp1ppp/8/4p3/6Pq/5P2/PPPPP2P/RNBQKBNR w KQkq - 1 3'}
+ ];
+ for (var i = 0; i < positions.length; i++) {
+ passed = true;
+ error_message = "";
+ for (var j = 0; j < positions[i].moves.length; j++) {
+ if (chess.move(positions[i].moves[j]) === null) {
+ error_message = "move() did not accept " + positions[i].moves[j] + " : ";
+ break;
+ }
+ }
+ var s = 'PGN Test #' + i + ': ' + error_message;
+ chess.info.apply(null, positions[i].info);
+ var pgn = chess.pgn({max_width:positions[i].max_width, newline_char:positions[i].newline_char});
+ var fen = chess.fen();
+ passed = pgn === positions[i].pgn && fen === positions[i].fen;
+ s += (passed) ? 'PASSED!' : 'FAILED!';
+ log(s);
+ chess.reset();
+ }
+ var finish = new Date;
+ var diff = (finish-start) / 1000;
+ log('--> PGN Time: ' + diff + ' secs');
+ log('');
+}
+
function make_move_tests() {
var chess = new Chess();
var start = new Date;
@@ -416,6 +478,7 @@ function run_unit_tests() {
algebraic_notation_tests();
get_put_remove_tests();
fen_tests();
+ pgn_tests();
make_move_tests();
var finish = new Date;
Please sign in to comment.
Something went wrong with that request. Please try again.