diff --git a/api/games/games.ts b/api/games/games.ts new file mode 100644 index 0000000000000000000000000000000000000000..a967dc798eb71dabe51627af8d1f8fad031401da --- /dev/null +++ b/api/games/games.ts @@ -0,0 +1,187 @@ +import { Mongo } from 'meteor/mongo' +import SimpleSchema from 'simpl-schema' +import { ValidatedMethod } from 'meteor/mdg:validated-method' +import { loggedInMixin, schemaMixin } from '../mixins' +import { Meteor } from 'meteor/meteor' +import { Card } from './cards' +import { Bet, GameSystem } from './logic' + +export const Games = new Mongo.Collection('games') + +export function notInGameMixin<A, R> (method: ValidatedMethod<A, R>) { + let run = method.run + method.run = function (...params: any[]) { + if (Games.find({'players.id': this.userId}).count() > 0) throw new Meteor.Error('already_in_game') + return run.call(this, ...params) + } + return method +} + +export function inGameMixin<A, R> (method: ValidatedMethod<A, R>) { + let run = method.run + method.run = function (...params: any[]) { + this.game = Games.findOne({'players.id': this.userId}) + if (this.game == null) throw new Meteor.Error('not_in_game') + this.game.sys = GameSystem.fromObject(this.game.sys) + return run.call(this, ...params) + } + return method +} + +const PlayerSchema = new SimpleSchema({ + id: String, + position: {type: Number, allowedValues: [0, 1, 2, 3]} +}) + +const GameSchema = new SimpleSchema({ + name: String, + players: {type: Array, defaultValue: [], optional: true}, + 'players.$': PlayerSchema, + sys: {type: GameSystem, defaultValue: new GameSystem(), optional: true} +}) + +interface Player { + id: string + position: number +} + +interface Game { + _id: string + name: string + players: Player[] + sys: GameSystem +} + +export const newGame = new ValidatedMethod<{ name: string }, string>({ + name: 'Game.new', + mixins: [schemaMixin, loggedInMixin, notInGameMixin], + validate: new SimpleSchema({ + name: String + }), + run (game) { + if (Games.find(game).count() === 0) { + let gameClean: Game = GameSchema.clean(game) as Game + gameClean.sys = gameClean.sys.toObject() + return Games.insert(gameClean) + } else { + throw new Meteor.Error('name_taken') + } + } +}) + +export const joinGame = new ValidatedMethod<{ _id: string }, void>({ + name: 'Game.join', + mixins: [schemaMixin, loggedInMixin, notInGameMixin], + validate: new SimpleSchema({ + _id: String + }), + run ({_id}) { + let game = Games.findOne({_id}) as Game + if (game == null) throw new Meteor.Error('game_not_found') + if (game.players.length === 4) throw new Meteor.Error('game_full') + game.players.push({id: this.userId, position: game.players.length}) + if (game.players.length === 4) { + game.sys = GameSystem.fromObject(game.sys) + game.sys.newRound() + game.sys = game.sys.toObject() + } + Games.update({_id}, {$set: game}) + } +}) + +export const nextCards = new ValidatedMethod<{}, void>({ + name: 'Game.nextCards', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({}), + run () { + callSystem(this, this.game.sys.nextCards) + } +}) + +export const giveCards = new ValidatedMethod<{ cards: Card[] }, void>({ + name: 'Game.giveCards', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({ + cards: {type: Array, minCount: 3, maxCount: 3}, + 'cards.$': Card + }), + run ({cards}) { + callSystem(this, this.game.sys.giveCards, cards) + } +}) + +export const playCards = new ValidatedMethod({ + name: 'Game.playCards', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({ + cards: [Card] + }), + run ({cards}) { + let player = this.game.players.findIndex(p => p.id === this.userId) + + if (!this.game.sys.playCards(player, cards)) { + throw new Meteor.Error('playCards') + } + + let winner = this.game.sys.winner() + + if (winner >= 0) { + Games.remove({_id: this.game._id}) + return winner + } else { + this.game.sys = this.game.sys.toObject() + Games.update({_id: this.game._id}, {$set: this.game}) + } + } +}) + +export const skipTurn = new ValidatedMethod<{}, void>({ + name: 'Game.skipTurn', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({}), + run () { + callSystem(this, this.game.sys.skipTurn) + } +}) + +export const giveDragon = new ValidatedMethod<{ to: number }, void>({ + name: 'game.giveDragon', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({ + to: {type: Number, allowedValues: [0, 1]} + }), + run ({to}) { + callSystem(this, this.game.sys.giveDragon, to) + } +}) + +export const makeCall = new ValidatedMethod<{ bet: number }, void>({ + name: 'Game.makeCall', + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: new SimpleSchema({ + bet: {type: Number, allowedValues: [Bet.TICHU, Bet.GRAND]} + }), + run ({bet}) { + callSystem(this, this.game.sys.makeCall, bet) + } +}) + +function newGameMethod (name: string, schema: SimpleSchema, argName?: string): ValidatedMethod<any, void> { + return new ValidatedMethod<any, void>({ + name: 'Game.' + name, + mixins: [schemaMixin, loggedInMixin, inGameMixin], + validate: schema, + run (arg) { + callSystem(this, this.game.sys[name], argName ? arg[argName] : null) + } + }) +} + +function callSystem<T> (self: { game: Game, userId: string }, method: (player: number, arg?: T) => boolean, arg?: T): void { + let player = self.game.players.findIndex(p => p.id === self.userId) + if (!method(player, arg)) { + throw new Meteor.Error(method.name) + } + let sysObject = self.game.sys.toObject() + Games.update({_id: self.game._id}, {$set: sysObject}) +}