Skip to content

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also .

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also .
...
Checking mergeability… Don’t worry, you can still create the pull request.
  • 9 commits
  • 8 files changed
  • 0 commit comments
  • 1 contributor
Commits on May 10, 2012
Chris Scribner Hooking up gnu go to play when there are no players and vote on moves…
… (bots vote doesn't count if 1+ human voters)

Reversed order of board letters as it was backwards
0384afc
Chris Scribner Switching port 4b36754
Commits on May 11, 2012
Chris Scribner Marking bot's moves so players can differentiate them e4b2c79
Chris Scribner Switching up the bot integration to send the whole board before every…
… move to see if it helps w/ synchronization issues
0ed8f39
Chris Scribner Switching port c519cbc
Chris Scribner Make it easier for players to join back in after going idle when the …
…bot is playing
afa6d92
Chris Scribner Don't allow the bot to vote illegally 262aa12
Chris Scribner Bug fix with clicking votes 1113700
Chris Scribner Bug fix with clicking votes 32298d1
Showing with 349 additions and 109 deletions.
  1. +140 −0 bot.js
  2. BIN client/gnugo
  3. +4 −7 kgs_gtp.js
  4. +79 −92 site/site.js
  5. +10 −0 site/web/css/board.css
  6. +16 −6 site/web/index.html
  7. +12 −4 site/web/js/controller/board.js
  8. +88 −0 utility.js
View
140 bot.js
@@ -0,0 +1,140 @@
+var path = require('path');
+var child_process = require('child_process');
+var utility = require('./utility.js');
+
+var proc;
+var waitingOnResponse = false;
+var sendQueue = [];
+
+exports.start = function(){
+ proc = child_process.spawn(
+ path.join(__dirname, './client/gnugo'),
+
+ ['--mode', 'gtp', '--level', '15'],
+
+ { cwd: path.join(__dirname, './client') }
+ );
+
+ proc.stdout.on('data', function(data){
+ console.error('bot stdout: ' + data);
+ });
+
+ proc.stderr.on('data', function(data){
+ console.error('bot stderr: ' + data);
+ });
+
+ proc.on('exit', function(code){
+ console.log('Exited ' + code);
+ });
+};
+
+exports.stop = function(){
+ proc.kill('SIGINT');
+};
+
+var processNextQueue = function(){
+ var next = sendQueue.splice(0, 1)[0];
+ if(next){
+ _send.apply(null, next);
+ }
+};
+
+var _send = function(command, callback){
+ console.log('send bot: ' + command);
+
+ var response = '';
+
+ proc.stdout.on('data', function(data){
+ var text = data.toString();
+ response += text;
+
+ console.log('bot: ' + text);
+
+ if(response.slice(-2) === '\n\n'){
+ proc.stdout.removeAllListeners('data');
+ waitingOnResponse = false;
+
+ if(callback){
+ callback(null, response);
+ }
+
+ processNextQueue();
+ }
+ });
+
+ waitingOnResponse = true;
+ proc.stdin.write(command + '\n\n');
+};
+
+exports.send = function(command, callback){
+ sendQueue.push(arguments);
+
+ if(waitingOnResponse){
+ return;
+ } else {
+ processNextQueue();
+ }
+};
+
+exports.register = function(commands){
+ var existing = {
+ genmove: commands.genmove,
+ play: commands.play,
+ boardsize: commands.boardsize,
+ clear_board: commands.clear_board,
+ komi: commands.komi,
+ 'tg-finalize-move': commands['tg-finalize-move']
+ };
+
+ var genmoveRegex = /=\s*([^\s]+)/;
+ commands.genmove = function(args, callback){
+ exports.send('genmove ' + args, function(err, raw){
+ var move = genmoveRegex.exec(raw)[1];
+ console.log('bot move: ' + move);
+
+ commands['tg-bot-vote'](move);
+ });
+
+ existing.genmove(args, callback);
+ };
+
+ commands.play = function(args, callback){
+ exports.send('play ' + args);
+
+ existing.play(args, callback);
+ };
+
+ commands.boardsize = function(args, callback){
+ exports.send('boardsize ' + args);
+
+ existing.boardsize(args, callback);
+ };
+
+ commands.komi = function(args, callback){
+ exports.send('komi ' + args);
+
+ existing.komi(args, callback);
+ };
+
+ commands.clear_board = function(args, callback){
+ exports.send('clear_board');
+
+ existing.clear_board(args, callback);
+ };
+
+ commands['tg-finalize-move'] = function(moves){
+ exports.send('clear_board');
+
+ moves.forEach(function(move){
+ if(!move.special){
+ exports.send('play ' + utility.toGtpColor(move.color) + ' ' + utility.toVertex(move.x, move.y));
+ }
+ });
+
+ existing['tg-finalize-move'](moves);
+ };
+};
+
+process.on("exit", function() {
+ exports.stop();
+});
View
BIN client/gnugo
Binary file not shown.
View
11 kgs_gtp.js
@@ -6,8 +6,6 @@ var proc;
var startOptions;
exports.start = function(options){
- console.log((new Error()).stack);
-
if(proc != null){
//already running
proc.removeAllListeners('exit');
@@ -26,15 +24,15 @@ exports.start = function(options){
startArgs.push('opponent=' + options.opponent);
}
- if(options.idle){
+ /*if(options.idle){
startArgs.push('mode=wait');
if(!options.opponent){
startArgs.push('opponent=zzzzzzzz');
}
- } else {
+ } else {*/
startArgs.push('mode=custom');
startArgs.push('gameNotes=I relay moves voted on by players at www.TeamGo.us' );
- }
+ //}
proc = child_process.spawn(
'java',
@@ -65,7 +63,7 @@ exports.start = function(options){
proc.stderr.on('data', function(data){
gtp.receiveError(data.toString());
- console.error('stderr: ' + data);
+ //console.error('stderr: ' + data);
});
proc.on('exit', function(code){
@@ -86,7 +84,6 @@ exports.start = function(options){
exports.stop = function(){
if(proc){
- console.log((new Error()).stack);
//proc.removeAllListeners('exit');
proc.kill('SIGINT');
View
171 site/site.js
@@ -3,6 +3,8 @@ var nowjs = require("now");
var async = require('async');
var gts_gtp = require('../kgs_gtp.js');
var kgs = require('../kgs.js');
+var bot = require('../bot.js');
+var utility = require('../utility.js');
var app = express.createServer();
@@ -46,6 +48,7 @@ everyone.now.login = function(sessionId, callback) {
this.now.setUsername(session.username);
}
+ session.lastVoteTime = new Date();
players.addUser(this.user.clientId);
callback();
@@ -108,90 +111,34 @@ var getReadyCount = function(callback){
});
};
-var vertexMapping = {
- a: 0,
- b: 1,
- c: 2,
- d: 3,
- e: 4,
- f: 5,
- g: 6,
- h: 7,
- j: 8,
- k: 9,
- l: 10,
- m: 11,
- n: 12,
- o: 13,
- p: 14,
- q: 15,
- r: 16,
- s: 17,
- t: 18,
-
- 0: 'a',
- 1: 'b',
- 2: 'c',
- 3: 'd',
- 4: 'e',
- 5: 'f',
- 6: 'g',
- 7: 'h',
- 8: 'j',
- 9: 'k',
- 10: 'l',
- 11: 'm',
- 12: 'n',
- 13: 'o',
- 14: 'p',
- 15: 'q',
- 16: 'r',
- 17: 's',
- 18: 't'
-};
-
-var rankMapping = (function(){
- var ranks = {};
- var i;
-
- for(i = 1; i <= 30; i++){
- ranks[i + 'k'] = i;
- }
- for(i = 1; i <= 9; i++){
- ranks[i + 'd'] = i + 30;
- }
- for(i = 1; i <= 9; i++){
- ranks[i + 'p'] = i + 39;
- }
-})();
+players.secondsPerMove = 27;
+players.votes = Object.create(null);
-var fromVertex = function(vertex){
- var firstChar = vertex[0].toLowerCase();
+var getMostVoted = function(){
+ var votes = {};
- return {x: vertexMapping[firstChar], y: 19 - parseInt(vertex.substring(1)) };
-};
+ var nonBotVoteExists = false;
+ Object.keys(players.votes).forEach(function(key){
+ var vote = players.votes[key];
-var toVertex = function(x, y){
- return vertexMapping[x] + (19 - parseInt(y));
-};
+ if(!vote.bot){
+ nonBotVoteExists = true;
+ }
+ });
-var fromGtpColor = function(color){
- color = color.toLowerCase();
+ //Remove bot votes if there are non-bot votes
+ if(nonBotVoteExists){
+ Object.keys(players.votes).forEach(function(key){
+ var vote = players.votes[key];
- if(color === 'w' || color === 'white'){
- return 'white';
- } else {
- return 'black';
+ if(vote.bot){
+ delete players.votes[key];
+ }
+ });
}
-};
-players.secondsPerMove = 27;
-players.votes = Object.create(null);
-
-var getMostVoted = function(){
- var votes = {};
Object.keys(players.votes).forEach(function(key){
var vote = players.votes[key];
if(vote.special == null){
@@ -290,7 +237,9 @@ var finalizeMove = function(color, callback){
var move = function(x, y, special){
players._genmove = null;
players._moves.push({color: color, x: x, y: y, special: special});
- players.now.playMove(color, x, y, special);
+ if(players.now.playMove){
+ players.now.playMove(color, x, y, special);
+ }
//Reset votes dictionary
players.votes = Object.create(null);
@@ -303,8 +252,10 @@ var finalizeMove = function(color, callback){
players.finalize = null;
if(special == null){
- callback(null, toVertex(x, y));
+ gts_gtp.gtp.commands['tg-finalize-move'](players._moves);
+ callback(null, utility.toVertex(x, y));
} else {
+ gts_gtp.gtp.commands['tg-finalize-move'](players._moves);
callback(null, special);
}
};
@@ -318,12 +269,16 @@ var finalizeMove = function(color, callback){
}
if(checkSpecialVote('pass') > 0.6){
- players.now.receiveChat('Game info', null, 'TeamGo passed');
+ if(players.now.receiveChat){
+ players.now.receiveChat('Game info', null, 'TeamGo passed');
+ }
return move(null, null, 'pass');
}
if(checkSpecialVote('resign') > 0.8){
- players.now.receiveChat('Game info', null, 'TeamGo resigned');
+ if(players.now.receiveChat){
+ players.now.receiveChat('Game info', null, 'TeamGo resigned');
+ }
players.now.inGame = false;
reset();
return move(null, null, 'resign');
@@ -345,10 +300,23 @@ var finalizeMove = function(color, callback){
}
};
+gts_gtp.gtp.commands['tg-finalize-move'] = function(){
+
+};
+
+gts_gtp.gtp.commands['tg-bot-vote'] = function(args){
+ var coord = utility.fromVertex(args);
+
+ if(players.now.addVote){
+ players.now.addVote(players.now.myColor, coord.x, coord.y, null, true);
+ }
+ players.votes['tg-bot'] = {x: coord.x, y: coord.y, bot: true };
+};
+
gts_gtp.gtp.commands.genmove = function(args, callback){
players.now.inGame = true;
- var color = fromGtpColor(args);
+ var color = utility.fromGtpColor(args);
players.now.whoseTurn = color;
players._genmove = arguments;
@@ -402,13 +370,13 @@ gts_gtp.gtp.commands.play = function(args, callback){
var match = playRegex.exec(args);
- var color = fromGtpColor(match[1]);
+ var color = utility.fromGtpColor(match[1]);
var coord = {};
var special;
if(match[2] === 'pass'){
special = 'pass';
} else {
- coord = fromVertex(match[2]);
+ coord = utility.fromVertex(match[2]);
}
if(players.now.playMove){
@@ -420,13 +388,17 @@ gts_gtp.gtp.commands.play = function(args, callback){
};
gts_gtp.gtp.commands['tg-final-score'] = function(args, callback){
- var color = fromGtpColor(args.winner);
+ var color = utility.fromGtpColor(args.winner);
color = color.substring(0, 1).toUpperCase() + color.substring(1);
if(args.resign){
- players.now.receiveChat('Game info', null, color + ' won the game by resignation.');
+ if(players.now.receiveChat){
+ players.now.receiveChat('Game info', null, color + ' won the game by resignation.');
+ }
} else {
- players.now.receiveChat('Game info', null, color + ' won the game by ' + args.score + ' points.');
+ if(players.now.receiveChat){
+ players.now.receiveChat('Game info', null, color + ' won the game by ' + args.score + ' points.');
+ }
}
callback();
@@ -481,7 +453,7 @@ var timeLeftRegex = /([bw]) (\d+)/i;
gts_gtp.gtp.commands['time_left'] = function(args, callback){
var match = timeLeftRegex.exec(args);
- var color = fromGtpColor(match[1]);
+ var color = utility.fromGtpColor(match[1]);
var seconds = parseInt(match[2]);
if(players.now.timeLeft == null){
@@ -583,10 +555,10 @@ gts_gtp.gtp.commands.boardsize = function(args, callback){
}
};
- if(gts_gtp.isIdle()){
+ /*if(gts_gtp.isIdle()){
//Refuse games until ready
return callback('unacceptable size');
- }
+ }*/
//If we don't want to play against the player, we can return an error on this call to abort the game
/*if(players.now.opponentName != null) {
@@ -647,6 +619,7 @@ setInterval(function(){
var currentTime = new Date();
var elapsedSeconds = Math.floor((currentTime - players.genMoveStarted) / 1000);
+ var totalVotes = getNumberOfVotes();
var numberConfirmed = getNumberOfConfirmedVotes();
var overallTurnTime = players.secondsPerMove - elapsedSeconds;
@@ -659,9 +632,21 @@ setInterval(function(){
players.now.turnTimeRemaining = Math.min(overallTurnTime, shortCircuit);
- if(players.now.whoseTurn === players.now.myColor && players.now.turnTimeRemaining <= 0){
- if(players.finalize){
- players.finalize();
+ if(players.now.whoseTurn === players.now.myColor){
+ if(players.now.activePlayerCount <= 0 && totalVotes > 0 && players.now.turnTimeRemaining < 15){
+ //Give players a chance to become active again (don't let the bot play too fast)
+ if(players.finalize){
+ players.finalize();
+ }
+ } else if(players.now.playerCount === 0 && totalVotes > 0){
+ //Just bot(s) logged in, no need to wait
+ if(players.finalize){
+ players.finalize();
+ }
+ } else if(players.now.turnTimeRemaining <= 0){
+ if(players.finalize){
+ players.finalize();
+ }
}
}
} else {
@@ -674,4 +659,6 @@ setInterval(function(){
}, 1000);
-gts_gtp.start({ idle: true });
+gts_gtp.start({ idle: true });
+bot.register(gts_gtp.gtp.commands);
+bot.start();
View
10 site/web/css/board.css
@@ -229,6 +229,16 @@
width: 2em;
}
+.board .bot-text {
+ position:absolute;
+ left: 8px;
+ font-size: 15px;
+ font-weight:bold;
+ text-shadow: white 0.1em 0.1em 0.2em;
+ -moz-user-select: none;
+ -webkit-user-select: none;
+}
+
.not-game-in-progress .board-letter {
display: none;
}
View
22 site/web/index.html
@@ -90,14 +90,14 @@ <h4 data-bind="visible: players.black.captures() > 0">
<div class="span8">
<div class="game" data-bind="visible: doneLoading, css: { 'game-in-progress': gameInProgress(), 'not-game-in-progress': !gameInProgress() }" style="display:none">
<div data-bind="visible: !gameInProgress()" class="well">
- <h3 data-bind="visible: !lookingForGame()">
+ <!--h3 data-bind="visible: !lookingForGame()">
TeamGo is not currently playing a game.<br />
When two or more players are ready, TeamGo will look for a game on KGS.
</h3>
<div data-bind="visible: !lookingForGame()" style="text-align: center;margin-top:1em">
<button data-bind="click: markReady, disable: isReady" class="btn btn-primary btn-large" style="font-size:20px">I'm Ready</button>
- </div>
- <h3 data-bind="visible: lookingForGame()">
+ </div-->
+ <h3>
TeamGo is currently looking for a game.
</h3>
</div>
@@ -127,8 +127,10 @@ <h3 data-bind="visible: lookingForGame()">
}
"
>
+
<div data-bind="css: { 'last-move': isLastMove() }, text: votes() > 0 ? votes() : ''"></div>
</div>
+ <div class="bot-text" data-bind="text: isBot() ? 'bot' : ''"></div>
</div>
</div>
</div></div>
@@ -252,8 +254,8 @@ <h3 data-bind="visible: lookingForGame()">
console.log(username);
};
- now.addVote = function(color, x, y, special){
- tg.board.addVote(color, x, y, special);
+ now.addVote = function(color, x, y, special, isBot){
+ tg.board.addVote(color, x, y, special, isBot);
};
now.confirmVote = function(x, y){
@@ -390,7 +392,7 @@ <h3 data-bind="visible: lookingForGame()">
}
var top = 0;
- for(var j = 1; j <= 19; j++){
+ for(var j = 19; j >= 1; j--){
var boardNumber = $('<div />')
.addClass('board-number')
.addClass('board-marker')
@@ -497,6 +499,14 @@ <h3 style="margin-top: 1em;margin-left:1em" >
</div>
<h3>
+ Who is the extra player?
+ </h3>
+ <div style="margin-left:1em">
+ A gnuGo bot also suggests moves and plays when no human players are available. If a human is playing, bot votes are not
+ taken into consideration when selecting moves to play.
+ </div>
+
+ <h3>
How can I contribute?
</h3>
<div style="margin-left:1em">
View
16 site/web/js/controller/board.js
@@ -97,6 +97,7 @@ var tg = {};
confirmed: ko.observable(false),
preview: ko.observable(false),
isLastMove: ko.observable(false),
+ isBot: ko.observable(false),
click: function(intersection){
self.events.emit('intersectionClick', intersection);
}
@@ -141,12 +142,18 @@ var tg = {};
this.players.black.captures(0);
};
- Board.prototype.addVote = function(color, x, y, special){
+ Board.prototype.addVote = function(color, x, y, special, isBot){
if(special == null){
var item = this.rows[y][x];
- item.preview(true);
- item.status(color);
- item.votes(item.votes() + 1);
+ if(item.status() === 'empty' || !isBot){
+ item.preview(true);
+ item.status(color);
+ item.votes(item.votes() + 1);
+ }
+
+ if(item.preview()){
+ item.isBot(item.isBot() || isBot);
+ }
} else {
this.votes[special](this.votes[special]() + 1);
}
@@ -186,6 +193,7 @@ var tg = {};
row[j].preview(false);
row[j].status('empty');
row[j].votes(0);
+ row[j].isBot(false);
}
}
}
View
88 utility.js
@@ -0,0 +1,88 @@
+var vertexMapping = {
+ a: 0,
+ b: 1,
+ c: 2,
+ d: 3,
+ e: 4,
+ f: 5,
+ g: 6,
+ h: 7,
+ j: 8,
+ k: 9,
+ l: 10,
+ m: 11,
+ n: 12,
+ o: 13,
+ p: 14,
+ q: 15,
+ r: 16,
+ s: 17,
+ t: 18,
+
+ 0: 'a',
+ 1: 'b',
+ 2: 'c',
+ 3: 'd',
+ 4: 'e',
+ 5: 'f',
+ 6: 'g',
+ 7: 'h',
+ 8: 'j',
+ 9: 'k',
+ 10: 'l',
+ 11: 'm',
+ 12: 'n',
+ 13: 'o',
+ 14: 'p',
+ 15: 'q',
+ 16: 'r',
+ 17: 's',
+ 18: 't'
+};
+
+var rankMapping = (function(){
+ var ranks = {};
+ var i;
+
+ for(i = 1; i <= 30; i++){
+ ranks[i + 'k'] = i;
+ }
+
+ for(i = 1; i <= 9; i++){
+ ranks[i + 'd'] = i + 30;
+ }
+
+ for(i = 1; i <= 9; i++){
+ ranks[i + 'p'] = i + 39;
+ }
+})();
+
+exports.fromVertex = function(vertex){
+ var firstChar = vertex[0].toLowerCase();
+
+ return {x: vertexMapping[firstChar], y: 19 - parseInt(vertex.substring(1)) };
+};
+
+exports.toVertex = function(x, y){
+ return vertexMapping[x] + (19 - parseInt(y));
+};
+
+exports.fromGtpColor = function(color){
+ color = color.toLowerCase();
+
+ if(color === 'w' || color === 'white'){
+ return 'white';
+ } else {
+ return 'black';
+ }
+};
+
+exports.toGtpColor = function(color){
+ color = color.toLowerCase();
+
+ if(color === 'white'){
+ return 'w';
+ } else {
+ return 'b';
+ }
+};

No commit comments for this range

Something went wrong with that request. Please try again.