Permalink
Browse files

change game from tic-tac-toe to nim

  • Loading branch information...
hiukim
hiukim committed Aug 9, 2016
1 parent 55d84fd commit f47b16a563189feb87efc13812db9df8c45695b7
View
@@ -1,11 +1,9 @@
table.game-board {
margin: auto;
tr td {
border: 1px solid black;
width: 80px;
height: 80px;
font-size: 80px;
text-align: center;
div.game-board {
span.object {
background: brown;
width: 50px;
height: 50px;
margin: 10px;
display: inline-block;
}
}
@@ -20,9 +20,9 @@ export let GamesController = {
Games.saveGame(game);
},
userMarkGame(gameId, user, row, col) {
userPickGame(gameId, user, pileIndex, count) {
let game = Games.findOne(gameId);
game.userMark(user, row, col);
game.userPick(user, pileIndex, count);
Games.saveGame(game);
}
}
@@ -28,14 +28,14 @@ export const userLeaveGame = new ValidatedMethod({
}
});
export const userMarkGame = new ValidatedMethod({
export const userPickGame = new ValidatedMethod({
name: 'games.userMarkGame',
validate: new SimpleSchema({
gameId: {type: String},
row: {type: Number},
col: {type: Number}
pileIndex: {type: Number},
count: {type: Number}
}).validator(),
run({gameId, row, col}) {
GamesController.userMarkGame(gameId, Meteor.user(), row, col);
run({gameId, pileIndex, count}) {
GamesController.userPickGame(gameId, Meteor.user(), pileIndex, count);
}
});
View
@@ -27,8 +27,15 @@ export class Game {
_.extend(this, gameDoc);
} else {
this.status = GameStatuses.WAITING;
this.board = [[null, null, null], [null, null, null], [null, null, null]];
// random number of piles, and random number objects in each piles
let numPiles = _.random(3, 5);
this.board = [];
for (let i = 0; i < numPiles; i++) {
this.board.push(_.random(3, 7));
}
this.players = [];
this.currentPlayerIndex = _.random(0, 1); //random starting player
}
}
@@ -38,7 +45,7 @@ export class Game {
* @return {[]String] List of fields required persistent storage
*/
persistentFields() {
return ['status', 'board', 'players'];
return ['status', 'board', 'players', 'currentPlayerIndex'];
}
/**
@@ -87,49 +94,32 @@ this.players.push({
}
}
/**
* Handle user action. i.e. putting marker on the game board
*
/**
* User pick
* @param {User} user
* @param {Number} row Row index of the board
* @param {Number} col Col index of the board
* @param {Number} pileIndex Index (0 based) of the pile user is picking
* @param {Number} count Number of objects picked
*/
userMark(user, row, col) {
userPick(user, pileIndex, count) {
let playerIndex = this.userIndex(user);
let currentPlayerIndex = this.currentPlayerIndex();
if (currentPlayerIndex !== playerIndex) {
if (this.currentPlayerIndex !== playerIndex) {
throw "user cannot make move at current state";
}
if (row < 0 || row >= this.board.length || col < 0 || col >= this.board[row].length) {
throw "invalid row|col input";
if (pileIndex < 0 || pileIndex >= this.board.length) {
throw "invalid pile";
}
if (this.board[row][col] !== null) {
throw "spot is filled";
if (count <= 0 || this.board[pileIndex] < count) {
throw "invalid count";
}
this.board[row][col] = playerIndex;
this.board[pileIndex] -= count;
let winner = this.winner();
if (winner !== null) {
this.status = GameStatuses.FINISHED;
}
if (this._filledCount() === 9) {
this.status = GameStatuses.FINISHED;
} else {
this.currentPlayerIndex = 1 - this.currentPlayerIndex;
}
}
/**
* @return {Number} currentPlayerIndex 0 or 1
*/
currentPlayerIndex() {
if (this.status !== GameStatuses.STARTED) {
return null;
}
// determine the current player by counting the filled cells
// if even, then it's first player, otherwise it's second player
let filledCount = this._filledCount();
return (filledCount % 2 === 0? 0: 1);
}
/**
* Determine the winner of the game
@@ -138,33 +128,10 @@ this.players.push({
*/
winner() {
let board = this.board;
for (let playerIndex = 0; playerIndex < 2; playerIndex++) {
// check rows
for (let r = 0; r < 3; r++) {
let allMarked = true;
for (let c = 0; c < 3; c++) {
if (board[r][c] !== playerIndex) allMarked = false;
}
if (allMarked) return playerIndex;
}
// check cols
for (let c = 0; c < 3; c++) {
let allMarked = true;
for (let r = 0; r < 3; r++) {
if (board[r][c] !== playerIndex) allMarked = false;
}
if (allMarked) return playerIndex;
}
// check diagonals
if (board[0][0] === playerIndex && board[1][1] === playerIndex && board[2][2] === playerIndex) {
return playerIndex;
}
if (board[0][2] === playerIndex && board[1][1] === playerIndex && board[2][0] === playerIndex) {
return playerIndex;
}
}
let remain = _.reduce(this.board, (memo, count) => {
return memo + count;
}, 0);
if (remain === 0) return this.currentPlayerIndex;
return null;
}
@@ -182,14 +149,4 @@ this.players.push({
}
return null;
}
_filledCount() {
let filledCount = 0;
for (let r = 0; r < 3; r++) {
for (let c = 0; c < 3; c++) {
if (this.board[r][c] !== null) filledCount++;
}
}
return filledCount;
}
}
View
@@ -1,33 +1,42 @@
import React, { Component } from 'react';
import GameHeader from './GameHeader.jsx';
import {Game, GameStatuses} from '../api/models/game.js';
import {userMarkGame} from '../api/methods/games.js';
import {userPickGame} from '../api/methods/games.js';
export default class GameBoard extends Component {
handleCellClick(row, col) {
let game = this.props.game;
if (game.currentPlayerIndex() !== game.userIndex(this.props.user)) return;
userMarkGame.call({gameId: game._id, row: row, col: col});
constructor(props) {
super(props);
let pickCounts = [];
for (let i = 0; i < props.game.board.length; i++) {
pickCounts.push(0);
}
this.state = {
pickCounts: pickCounts,
}
}
handleBackToGameList() {
this.props.backToGameListHandler();
}
renderCell(row, col) {
let value = this.props.game.board[row][col];
if (value === 0) return (<td>O</td>);
if (value === 1) return (<td>X</td>);
if (value === null) return (
<td onClick={this.handleCellClick.bind(this, row, col)}></td>
);
handlePickCountsChange(pileIndex, e) {
let newPickCounts = [];
for (let i = 0; i < this.state.pickCounts.length; i++) {
newPickCounts.push(this.state.pickCounts[i]);
}
newPickCounts[pileIndex] = e.target.value;
this.setState({pickCounts: newPickCounts});
}
handlePick(pileIndex) {
userPickGame.call({gameId: this.props.game._id, pileIndex: pileIndex, count: parseInt(this.state.pickCounts[pileIndex])});
}
renderStatus() {
let game = this.props.game;
let status = "";
if (game.status === GameStatuses.STARTED) {
let playerIndex = game.currentPlayerIndex();
let playerIndex = game.currentPlayerIndex;
status = `Current player: ${game.players[playerIndex].username}`;
} else if (game.status === GameStatuses.FINISHED) {
let playerIndex = game.winner();
@@ -54,13 +63,13 @@ export default class GameBoard extends Component {
<div className="ui grid">
<div className="ui three column center aligned row">
<div className="ui column">
{this.props.game.players[0].username} <br/>O
{this.props.game.players[0].username}
</div>
<div className="ui column">
v.s.
</div>
<div className="ui column">
{this.props.game.players[1].username} <br/>X
{this.props.game.players[1].username}
</div>
</div>
</div>
@@ -70,25 +79,26 @@ export default class GameBoard extends Component {
</div>
<div className="ui attached segment">
<table className="game-board">
<tbody>
<tr>
{this.renderCell(0, 0)}
{this.renderCell(0, 1)}
{this.renderCell(0, 2)}
</tr>
<tr>
{this.renderCell(1, 0)}
{this.renderCell(1, 1)}
{this.renderCell(1, 2)}
</tr>
<tr>
{this.renderCell(2, 0)}
{this.renderCell(2, 1)}
{this.renderCell(2, 2)}
</tr>
</tbody>
</table>
<div className="game-board">
{this.props.game.board.map((pileCount, pileIndex) => {
return (
<div key={pileIndex}>
{_.range(pileCount).map((index) => {
return (
<span key={index} className="object">&nbsp;</span>
)
})}
{/* only in player turn and remains > 0 */}
{pileCount > 0 && this.props.game.currentPlayerIndex === this.props.game.userIndex(this.props.user)? (
<span>
<input size="2" type="text" onChange={this.handlePickCountsChange.bind(this, pileIndex)} value={this.state.pickCounts[pileIndex]}/> <button className="ui button" onClick={this.handlePick.bind(this, pileIndex)}>Pick</button>
</span>
): null}
</div>
)
})}
</div>
</div>
</div>
)

0 comments on commit f47b16a

Please sign in to comment.