Files
meteor/examples/wordplay/model.js
2012-04-10 11:10:47 -07:00

143 lines
4.3 KiB
JavaScript

////////// Shared code (client and server) //////////
Games = new Meteor.Collection('games');
// { board: ['A','I',...], clock: 60,
// players: [{player_id, name}], winners: [player_id] }
Words = new Meteor.Collection('words');
// {player_id: 10, game_id: 123, word: 'hello', state: 'good', score: 4}
Players = new Meteor.Collection('players');
// {name: 'matt', game_id: 123}
// 6 faces per die, 16 dice. Q really means Qu.
var DICE = ['PCHOAS', 'OATTOW', 'LRYTTE', 'VTHRWE',
'EGHWNE', 'SEOTIS', 'ANAEEG', 'IDSYTT',
'MTOICU', 'AFPKFS', 'XLDERI', 'ENSIEU',
'YLDEVR', 'ZNRNHL', 'NMIQHU', 'OBBAOJ'];
// board is an array of length 16, in row-major order. ADJACENCIES
// lists the board positions adjacent to each board position.
var ADJACENCIES = [
[1,4,5],
[0,2,4,5,6],
[1,3,5,6,7],
[2,6,7],
[0,1,5,8,9],
[0,1,2,4,6,8,9,10],
[1,2,3,5,7,9,10,11],
[2,3,6,10,11],
[4,5,9,12,13],
[4,5,6,8,10,12,13,14],
[5,6,7,9,11,13,14,15],
[6,7,10,14,15],
[8,9,13],
[8,9,10,12,14],
[9,10,11,13,15],
[10,11,14]
];
// generate a new random selection of letters.
var new_board = function () {
var board = [];
var i;
// pick random letter from each die
for (i = 0; i < 16; i += 1) {
board[i] = DICE[i].split('')[Math.floor(Math.random() * 6)];
}
// knuth shuffle
for (i = 15; i > 0; i -= 1) {
var j = Math.floor(Math.random() * (i + 1));
var tmp = board[i];
board[i] = board[j];
board[j] = tmp;
}
return board;
};
// returns an array of valid paths to make the specified word on the
// board. each path is an array of board positions 0-15. a valid
// path can use each position only once, and each position must be
// adjacent to the previous position.
var paths_for_word = function (board, word) {
var valid_paths = [];
var check_path = function (word, path, positions_to_try) {
// base case: the whole word has been consumed. path is valid.
if (word.length === 0) {
valid_paths.push(path);
return;
}
// otherwise, try to match each available position against the
// first letter of the word, avoiding any positions that are
// already used by the path. for each of those matches, descend
// recursively, passing the remainder of the word, the accumulated
// path, and the positions adjacent to the match.
for (var i = 0; i < positions_to_try.length; i++) {
var pos = positions_to_try[i];
if (board[pos] === word[0] && path.indexOf(pos) === -1)
check_path(word.slice(1), // cdr of word
path.concat([pos]), // append matching loc to path
ADJACENCIES[pos]); // only look at surrounding tiles
}
};
// start recursive search w/ full word, empty path, and all tiles
// available for the first letter.
check_path(word, [], [0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
return valid_paths;
};
Meteor.methods({
score_word: function (word_id) {
var word = Words.findOne(word_id);
var game = Games.findOne(word.game_id);
// client and server can both check: must be at least three chars
// long, not already used, and possible to make on the board.
if (word.length < 3
|| Words.find({game_id: word.game_id, word: word.word}).count() > 1
|| paths_for_word(game.board, word.word).length === 0) {
Words.update(word._id, {$set: {score: 0, state: 'bad'}});
return;
}
// now only on the server, check against dictionary and score it.
if (Meteor.is_server) {
if (DICTIONARY.indexOf(word.word.toLowerCase()) === -1) {
Words.update(word._id, {$set: {score: 0, state: 'bad'}});
} else {
var score = Math.pow(2, word.word.length - 3);
Words.update(word._id, {$set: {score: score, state: 'good'}});
}
}
}
});
if (Meteor.is_server) {
// publish all the non-idle players.
Meteor.publish('players', function () {
return Players.find({idle: false});
});
// publish single games
Meteor.publish('games', function (id) {
return Games.find({_id: id});
});
// publish all my words and opponents' words that the server has
// scored as good.
Meteor.publish('words', function (game_id, player_id) {
return Words.find({$or: [{game_id: game_id, state: 'good'},
{player_id: player_id}]});
});
}