Commit 9ffff8da authored by JoyJ's avatar JoyJ

Initial commit

parents
Pipeline #17300 canceled with stages
node_modules
tags
##反人类卡牌游戏
一种桌游。几个人轮流担任裁判,未担任裁判的人以手中的白卡与公用的黑卡一起组成句子。
裁判判定谁的句子最逆天,并为那个人加1分。
数轮过后,分数最高者胜;或第一个达到某个设定分数的人胜利。
(不过这里没有胜利一说)
##这是什么
一个用NodeJS实现的反人类卡牌。
<a href="https://github.com/coridrew/nodejs-against-humanity">原版repo</a>
##安装运行
npm install
node server.js
默认端口为3000(可修改进程变量PORT进行更改)
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.0/"><img alt="Creative Commons License" style="border-width:0" src="http://i.creativecommons.org/l/by-nc-sa/2.0/88x31.png" /></a><br />This work is licensed under a <a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">Creative Commons Attribution-NonCommercial-ShareAlike 2.0 Generic License</a>.
This diff is collapsed.
var _ = require('underscore');
var cards = require('./cards.js');
var gameList = [];
function getDeck() {
try {
return cards.getDeck();
}
catch{
return null;
}
}
function removeFromArray(array, item) {
try {
var index = array.indexOf(item);
if(index != -1) array.splice(index, 1);
}
catch{
return null;
}
}
function list() {
try {
return toInfo(_.filter(gameList, function(x) {
return x.players.length < x.maxPlayers;
}));
}
catch{
return null;
}
}
function listAll() {
try {
return toInfo(gameList);
}
catch{
return null;
}
}
function toInfo(fullGameList) {
try {
return _.map(fullGameList, function(game) {
return { id: game.id, name: game.name, players: game.players.length, maxPlayers:game.maxPlayers };
});
}
catch{
return null;
}
}
function addGame(game) {
try{
game.players = [];
game.history = [];
game.isOver = false;
game.winnerId = null;
game.winningCardId = null;
game.isStarted = false;
game.deck = getDeck();
game.currentBlackCard = "";
game.isReadyForScoring = false;
game.isReadyForReview = false;
game.Judge = 0;
//game.pointsToWin = 5;
gameList.push(game);
return game;
}
catch {
return null;
}
}
function getGame(gameId) {
try {
return _.find(gameList, function(x) { return x.id == gameId; });
}
catch {
return null;
}
}
function joinGame(game, player) {
try {
game.players.push({
id: player.id,
name: player.name,
isReady: false,
selectedWhiteCardId: null,
awesomePoints: 0,
});
if(game.players.length == game.maxPlayers) {
startGame(game);
}
return game;
}
catch {
return null;
}
}
function startGame(game) {
try {
game.isStarted = true;
setCurrentBlackCard(game);
_.each(game.players, function(player) {
player.cards = [];
for(var i = 0; i < 7; i++) {
drawWhiteCard(game, player);
}
});
}
catch {
return null;
}
}
function roundEnded(game) {
try {
game.winnerId = null;
game.winningCardId = null;
game.isReadyForScoring = false;
game.isReadyForReview = false;
setCurrentBlackCard(game);
_.each(game.players, function(player) {
if(!game.players[game.Judge] != player) {
removeFromArray(player.cards, player.selectedWhiteCardId);
drawWhiteCard(game, player);
}
player.isReady = false;
player.selectedWhiteCardId = null;
});
game.Judge++;
if (game.Judge >= player.length) {
game.Judge = 0;
}
game.players[game.Judge] = false;
}
catch {
return null;
}
}
function drawWhiteCard(game, player) {
try {
var whiteIndex = Math.floor(Math.random() * game.deck.white.length);
player.cards.push(game.deck.white[whiteIndex]);
game.deck.white.splice(whiteIndex, 1);
}
catch {
return null;
}
}
function setCurrentBlackCard(game) {
try {
var index = Math.floor(Math.random() * game.deck.black.length);
game.currentBlackCard = game.deck.black[index];
game.deck.black.splice(index, 1);
}
catch {
return null;
}
}
function getPlayer(gameId, playerId) {
try {
var game = getGame(gameId);
return _.find(game.players, function(x) { return x.id == playerId; });
}
catch {
return null;
}
}
function getPlayerByCardId(gameId, cardId) {
try {
var game = getGame(gameId);
return _.findWhere(game.players, { selectedWhiteCardId: cardId });
}
catch {
return null;
}
}
function readyForNextRound(gameId, playerId) {
try{
var player = getPlayer(gameId, playerId);
player.isReady = true;
var game = getGame(gameId);
var allReady = _.every(game.players, function(x) {
return x.isReady;
});
if(allReady) roundEnded(game);
}
catch {
return null;
}
}
function selectCard(gameId, playerId, whiteCardId) {
try {
var player = getPlayer(gameId, playerId);
player.selectedWhiteCardId = whiteCardId;
player.isReady = false;
var game = getGame(gameId);
var readyPlayers = _.filter(game.players, function (x) {
return x.selectedWhiteCardId;
});
if(readyPlayers.length == (game.players.length - 1))
game.isReadyForScoring = true;
}
catch{
return null;
}
}
function selectWinner(gameId, cardId) {
try{
var player = getPlayerByCardId(gameId, cardId);
var game = getGame(gameId);
game.winningCardId = cardId;
game.isReadyForReview = true;
player.awesomePoints = player.awesomePoints + 1;
game.history.push({ black: game.currentBlackCard, white: cardId, winner: player.name });
}
catch{
return null;
}
/*if(player.awesomePoints == game.pointsToWin) {
var game = getGame(gameId);
game.isOver = true;
game.winnerId = player.id;
}*/
}
function reset(){
gameList = [];
}
exports.list = list;
exports.listAll = listAll;
exports.addGame = addGame;
exports.getGame = getGame;
exports.getPlayer = getPlayer;
exports.joinGame = joinGame;
exports.readyForNextRound = readyForNextRound;
exports.reset = reset;
exports.roundEnded = roundEnded;
exports.selectCard = selectCard;
exports.selectWinner = selectWinner;
exports.removeFromArray = removeFromArray;
exports.getDeck = getDeck;
This diff is collapsed.
{
"name": "nodejs-against-humanity",
"version": "1.0.0-13",
"description": "Cards Against Humanities implemented in NodeJS",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"repository": {
"type": "git",
"url": "git://github.com/amirrajan/nodejs-against-humanity.git"
},
"dependencies": {
"express": "~3.0.1",
"ejs": "~0.8.3",
"jasmine-node": "~1.0.26",
"underscore": "~1.4.2",
"socket.io": "~0.9.11",
"backbone": "~0.9.2",
"qs": "~0.6.5",
"oauth": "~0.9.10",
"redis": "~0.8.4",
"redis-url": "0.1.0",
"request": "~2.27.0"
},
"subdomain": "nah-amir-rajan",
"engines": {
"node": "0.10.x"
}
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
/*jshint eqnull:true */
/*!
* jQuery Cookie Plugin v1.2
* https://github.com/carhartl/jquery-cookie
*
* Copyright 2011, Klaus Hartl
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://www.opensource.org/licenses/mit-license.php
* http://www.opensource.org/licenses/GPL-2.0
*/
(function ($, document, undefined) {
var pluses = /\+/g;
function raw(s) {
return s;
}
function decoded(s) {
return decodeURIComponent(s.replace(pluses, ' '));
}
var config = $.cookie = function (key, value, options) {
// write
if (value !== undefined) {
options = $.extend({}, config.defaults, options);
if (value === null) {
options.expires = -1;
}
if (typeof options.expires === 'number') {
var days = options.expires, t = options.expires = new Date();
t.setDate(t.getDate() + days);
}
value = config.json ? JSON.stringify(value) : String(value);
return (document.cookie = [
encodeURIComponent(key), '=', config.raw ? value : encodeURIComponent(value),
options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
options.path ? '; path=' + options.path : '',
options.domain ? '; domain=' + options.domain : '',
options.secure ? '; secure' : ''
].join(''));
}
// read
var decode = config.raw ? raw : decoded;
var cookies = document.cookie.split('; ');
for (var i = 0, parts; (parts = cookies[i] && cookies[i].split('=')); i++) {
if (decode(parts.shift()) === key) {
var cookie = decode(parts.join('='));
return config.json ? JSON.parse(cookie) : cookie;
}
}
return null;
};
config.defaults = {};
$.removeCookie = function (key, options) {
if ($.cookie(key) !== null) {
$.cookie(key, null, options);
return true;
}
return false;
};
})(jQuery, document);
\ No newline at end of file
This diff is collapsed.
/* =============================================================
* bootstrap-collapse.js v2.0.4
* http://twitter.github.com/bootstrap/javascript.html#collapse
* =============================================================
* Copyright 2012 Twitter, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ============================================================ */
!function ($) {
"use strict"; // jshint ;_;
/* COLLAPSE PUBLIC CLASS DEFINITION
* ================================ */
var Collapse = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.collapse.defaults, options)
if (this.options.parent) {
this.$parent = $(this.options.parent)
}
this.options.toggle && this.toggle()
}
Collapse.prototype = {
constructor: Collapse
, dimension: function () {
var hasWidth = this.$element.hasClass('width')
return hasWidth ? 'width' : 'height'
}
, show: function () {
var dimension
, scroll
, actives
, hasData
if (this.transitioning) return
dimension = this.dimension()
scroll = $.camelCase(['scroll', dimension].join('-'))
actives = this.$parent && this.$parent.find('> .accordion-group > .in')
if (actives && actives.length) {
hasData = actives.data('collapse')
if (hasData && hasData.transitioning) return
actives.collapse('hide')
hasData || actives.data('collapse', null)
}
this.$element[dimension](0)
this.transition('addClass', $.Event('show'), 'shown')
this.$element[dimension](this.$element[0][scroll])
}
, hide: function () {
var dimension
if (this.transitioning) return
dimension = this.dimension()
this.reset(this.$element[dimension]())
this.transition('removeClass', $.Event('hide'), 'hidden')
this.$element[dimension](0)
}
, reset: function (size) {
var dimension = this.dimension()
this.$element
.removeClass('collapse')
[dimension](size || 'auto')
[0].offsetWidth
this.$element[size !== null ? 'addClass' : 'removeClass']('collapse')
return this
}
, transition: function (method, startEvent, completeEvent) {
var that = this
, complete = function () {
if (startEvent.type == 'show') that.reset()
that.transitioning = 0
that.$element.trigger(completeEvent)
}
this.$element.trigger(startEvent)
if (startEvent.isDefaultPrevented()) return
this.transitioning = 1
this.$element[method]('in')
$.support.transition && this.$element.hasClass('collapse') ?
this.$element.one($.support.transition.end, complete) :
complete()
}
, toggle: function () {
this[this.$element.hasClass('in') ? 'hide' : 'show']()
}
}
/* COLLAPSIBLE PLUGIN DEFINITION
* ============================== */
$.fn.collapse = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('collapse')
, options = typeof option == 'object' && option
if (!data) $this.data('collapse', (data = new Collapse(this, options)))
if (typeof option == 'string') data[option]()
})
}
$.fn.collapse.defaults = {
toggle: true
}
$.fn.collapse.Constructor = Collapse
/* COLLAPSIBLE DATA-API
* ==================== */
$(function () {
$('body').on('click.collapse.data-api', '[data-toggle=collapse]', function ( e ) {
var $this = $(this), href
, target = $this.attr('data-target')
|| e.preventDefault()
|| (href = $this.attr('href')) && href.replace(/.*(?=#[^\s]+$)/, '') //strip for ie7
, option = $(target).data('collapse') ? 'toggle' : $this.data()
$(target).collapse(option)
})
})
}(window.jQuery);
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
var express = require('express');
var app = express()
var server = require('http').createServer(app);
var Game = require('./game.js')
var players = { };
var io = require('socket.io').listen(server);
var _ = require('underscore');
server.listen(process.env.PORT || 3000);
app.set('view engine', 'ejs');
app.set('view options', { layout: false });
app.use(express.methodOverride());
app.use(express.bodyParser());
app.use(app.router);
app.use('/public', express.static('public'));
function json(o, res) {
try{
res.writeHead(200, { 'Content-Type': 'application/json' });
res.write(JSON.stringify(o));
res.end();
}
catch {
return null;
}
}
function returnGame(gameId, res) { json(gameViewModel(gameId), res); }
function broadcastGame(gameId) {
try{
for(var player in players[gameId]) {
players[gameId][player].emit("updateGame", gameViewModel(gameId));
}
}
catch {
return null;
}
}
function gameViewModel(gameId) {
var game = Game.getGame(gameId);
try{
var viewModel = JSON.parse(JSON.stringify(game));
delete viewModel.deck;
return viewModel;
}
catch{
return null;
}
}
io.sockets.on('connection', function(socket) {
socket.on('connectToGame', function(data) {
try {
if(!players[data.gameId]) players[data.gameId] = { };
socket.gameId = data.gameId;
socket.playerId = data.playerId;
players[data.gameId][data.playerId] = socket;
broadcastGame(data.gameId);
}
catch{
return null;
}
});
socket.on('disconnect', function() {
try {
delete players[socket.gameId][socket.playerId];
}
catch{
return null;
}
});
socket.on('error',(err)=>{
try {
socket.destory();
}
catch{
return null;
}
})
})
io.sockets.on('error',(err)=>{
try {
socket.destory();
}
catch{
return null;
}
})
app.get('/', function (req, res) { res.render('index'); });
app.get('/game', function (req, res) { res.render('game'); });
app.get('/list', function (req, res) { json(Game.list(), res); });
app.get('/listall', function (req, res) { json(Game.listAll(), res); });
app.post('/add', function (req, res) { json(Game.addGame(req.body), res); });
app.get('/gamebyid', function (req, res) { json(Game.getGame(req.query.id), res); });
app.post('/joingame', function (req, res) {
try{
var game = Game.getGame(req.body.gameId);
if (game == null){
return null;
}
if(game.isStarted) {
res.writeHead(500, { 'Content-Type': 'application/json' });
res.write(JSON.stringify({ error: "too many players" }));
res.end();
return null;
}
game = Game.joinGame(game, { id: req.body.playerId, name: req.body.playerName });
returnGame(req.body.gameId, res);
}
catch{
return null;
}
});
app.post('/selectcard', function(req, res) {
try{
Game.selectCard(req.body.gameId, req.body.playerId, req.body.whiteCardId);
broadcastGame(req.body.gameId);
returnGame(req.body.gameId, res);
}
catch{
return null;
}
});
app.post('/selectWinner', function(req, res) {
try {
Game.selectWinner(req.body.gameId, req.body.cardId);
broadcastGame(req.body.gameId);
returnGame(req.body.gameId, res);
}
catch{
return null;
}
});
app.post('/readyForNextRound', function(req, res){
try {
Game.readyForNextRound(req.body.gameId, req.body.playerId);
broadcastGame(req.body.gameId);
returnGame(req.body.gameId, res);
}
catch{
return null;
}
});
This diff is collapsed.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>决斗编年史-反人类卡牌分部</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="">
<meta name="author" content="">
<script src="/public/jquery.js" type="text/javascript"></script>
<script src="/public/jqueryCookie.js" type="text/javascript"></script>
<script src="/public/jqueryTmpl.js" type="text/javascript"></script>
<script src="/public/underscore.js" type="text/javascript"></script>
<script src="/public/js/bootstrap-collapse.js" type="text/javascript"></script>
<link href="/public/css/bootstrap.css" rel="stylesheet">
<style>
body {
padding-top: 60px;
}
</style>
<link href="/public/css/bootstrap-responsive.css" rel="stylesheet">
</head>
<body>
<div class="navbar navbar-fixed-top">
<div class="navbar-inner">
<div class="container">
<a class="btn btn-navbar" data-toggle="collapse" data-target=".nav-collapse">
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</a>
<a class="brand">反人类卡牌游戏</a>
<div class="nav-collapse collapse">
<ul class="nav">
<li><a href="javascript:createGame();">建立房间</a></li>
</ul>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<a rel="license" href="http://creativecommons.org/licenses/by-nc-sa/2.0/">
<img alt="Creative Commons License" style="display: block; margin-left: auto; margin-right: auto;" src="http://i.creativecommons.org/l/by-nc-sa/2.0/88x31.png" /></a>
<br /></div></hr>
<div class="row">
本站与反人类卡牌官方毫无关系。
</div>
<h3>房间列表(点击加入游戏)</h3>
<hr />
<div class="row" id="availableGames">
</div>
</div>
</body>
<script type="text/javascript">
var playerId = GUID();
var gameId = GUID();
$(document).ready(function() {
PollForGames();
});
function PollForGames() {
$('#availableGames').html('');
GetGames();
setTimeout(PollForGames, 5000);
}
function GetGames(){
var gameTemplate = '<div class="well well-small" style="font-size: 16px"> <a href="javascript:;" data-gameId="${gameId}">${name} - 玩家数: ${players} / ${maxPlayers}</a> </div>';
$.getJSON("list", function(games) {
if(games.length == 0) {
$("#availableGames").append("<div style='font-weight: bold'>目前没有房间。点击页面上方的【建立房间】以建立一个新的。</div>");
} else {
jQuery.each(games, function(game) {
var gameElement = $.tmpl(gameTemplate, {
gameId: games[game].id,
name: games[game].name,
players: games[game].players,
maxPlayers: games[game].maxPlayers,
});
$(gameElement).click(function() {
var name = prompt("你的玩家名是?", "路过的假面骑士" + S4());
if (name.replace(/\s/g, "").length == 0) {
name = "路过的假面骑士";
}
var gameId = $(gameElement).find('a').attr('data-gameId');
$.post("joingame", { gameId: gameId, playerId: playerId, playerName: name }, function() {
window.location.replace("/game?gameId=" + gameId + "&playerId=" + playerId);
});
});
$('#availableGames').append(gameElement);
});
}
});
}
function createGame() {
var name = prompt("你的玩家名是?", "路过的假面骑士" + S4());
if (name.replace(/\s/g, "").length == 0) {
name = "路过的假面骑士";
}
var num = prompt("建一个几人房?(必须在4以上,20以下)", "4");
num = Number(num);
if (num == NaN || num < 4) {
num = 4;
}
if (num > 20){
num = 20;
}
$.post("add", { id: gameId, name: name + "的游戏", maxPlayers: num }, function() {
$.post("joingame", { gameId: gameId, playerId: playerId, playerName: name}, function() {
window.location.replace("/game?gameId=" + gameId + "&playerId=" + playerId);
});
});
};
function S4() {
return Math.floor(Math.random() * 0x10000).toString();
}
function GUID(){
return S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4();
};
</script>
</html>
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment