Definition mixins
kai zhu
kaizhu256 at gmail.com
Sun Nov 5 15:27:04 UTC 2017
the problem is that you chose to write the chess program in
javascript, instead of say, python or c#.
why did you choose javascript? probably because you intend the chess
program to be an online webapp. mixins, like classes, are inferior to
plain json-objects for webapps. how do you intend to serialize the
mixin board-state so you can sync it with a friend in an online match?
the i/o part of a webapp is typically as challenging as the
business-logic of the game itself.
it would be more efficient if you represented the board-state as a
json-object, so it can be easily serailized and shared with other
online players, and use static functions to manipulate the json-data.
here's a functional web-demo of a simple connect4 game using only
json-objects and static functions (in 400 lines of code).
-kai
https://github.com/kaizhu256/node-connect4
```js
/*
* test.js
*
* this file contains the standalone connect-4 game
*
* setup instructions
* 1. save this file as test.js
* 2. install nodejs
* 3. run the shell command
* $ PORT=8081 node test.js
* 4. open browser to url http://localhost:8081
* 5. play the connect4 game!
*/
/*jslint
bitwise: true,
browser: true,
maxerr: 8,
maxlen: 96,
node: true,
nomen: true,
regexp: true,
stupid: true
*/
(function () {
'use strict';
var local;
// run shared js-env code - pre-init
(function () {
// init local
local = {};
// init modeJs
local.modeJs = (function () {
try {
return typeof navigator.userAgent === 'string' &&
typeof document.querySelector('body') === 'object' &&
typeof XMLHttpRequest.prototype.open === 'function' &&
'browser';
} catch (errorCaughtBrowser) {
return module.exports &&
typeof process.versions.node === 'string' &&
typeof require('http').createServer === 'function' &&
'node';
}
}());
// init global
local.global = local.modeJs === 'browser'
? window
: global;
local.nop = function () {
/*
* this function will do nothing
*/
return;
};
// export local
local.global.local = local;
}());
// run shared js-env code - function
(function () {
local.gameStateCreate = function () {
/*
* this function will create a new game state
*/
var state;
state = {};
state.board = [
// -> rows
[0, 0, 0, 0, 0, 0], // |
[0, 0, 0, 0, 0, 0], // v
[0, 0, 0, 0, 0, 0], //
[0, 0, 0, 0, 0, 0], // c
[0, 0, 0, 0, 0, 0], // o
[0, 0, 0, 0, 0, 0], // l
[0, 0, 0, 0, 0, 0] // s
];
state.playerCurrent = 1;
state.streakToWin = 4;
return state;
};
local.playerMove = function (state, positionCol) {
/*
* this function will perform a move
* by dropping the state.playerCurrent's disc in the given positionCol,
* and then checks to see if it wins the game
*/
var colList, ii, positionRow, streak;
if (state.ended) {
state.error = new Error('game ended');
}
if (state.error) {
// debug error
console.error(state.error.stack);
return;
}
if (positionCol === 'random') {
while (true) {
positionCol = Math.floor(Math.random() *
state.board.length);
colList = state.board[positionCol] || [];
if (colList[colList.length - 1] === 0) {
break;
}
}
}
state.positionCol = positionCol;
colList = state.board[positionCol] || [];
// naive algorithm to deposit disc in the last unfilled
positionRow in colList
for (ii = 0; ii < colList.length; ii += 1) {
if (colList[ii] === 0) {
positionRow = ii;
colList[positionRow] = state.playerCurrent;
// debug board
console.log(state.board.join('\n'));
break;
}
}
if (positionRow === undefined) {
state.error = new Error('invalid move');
// debug error
console.error(state.error.stack);
return;
}
// naive algorithm to check for win condition in the column
// e.g.
// [
// -> rows
// [1, 1, 1, 1, 0, 0], // |
// [2, 2, 2, 0, 0, 0], // v
// [0, 0, 0, 0, 0, 0], //
// [0, 0, 0, 0, 0, 0], // c
// [0, 0, 0, 0, 0, 0], // o
// [0, 0, 0, 0, 0, 0], // l
// [0, 0, 0, 0, 0, 0] // s
// ]
streak = 0;
// iterate through the column
for (ii = 0; ii < colList.length; ii += 1) {
if (colList[ii] === state.playerCurrent) {
streak += 1;
if (streak >= 4) {
state.ended = state.playerCurrent;
return;
}
} else {
streak = 0;
}
}
// naive algorithm to check for win condition in the row
// e.g.
// [
// -> rows
// [1, 2, 0, 0, 0, 0], // |
// [1, 2, 0, 0, 0, 0], // v
// [1, 2, 0, 0, 0, 0], //
// [1, 0, 0, 0, 0, 0], // c
// [0, 0, 0, 0, 0, 0], // o
// [0, 0, 0, 0, 0, 0], // l
// [0, 0, 0, 0, 0, 0] // s
// ]
streak = 0;
// iterate through the row
for (ii = 0; ii < state.board.length; ii += 1) {
if (state.board[ii][positionRow] === state.playerCurrent) {
streak += 1;
if (streak >= 4) {
state.ended = state.playerCurrent;
return;
}
} else {
streak = 0;
}
}
// naive algorithm to check for win condition in the upward diagonal
// e.g.
// [
// -> rows
// [1, 2, 0, 0, 0, 0], // |
// [2, 1, 0, 0, 0, 0], // v
// [2, 1, 1, 0, 0, 0], //
// [2, 2, 1, 1, 0, 0], // c
// [0, 0, 0, 0, 0, 0], // o
// [0, 0, 0, 0, 0, 0], // l
// [0, 0, 0, 0, 0, 0] // s
// ]
streak = 0;
// iterate through the row
for (ii = 0; ii < state.board.length; ii += 1) {
if (state.board[ii][positionRow + ii - positionCol] ===
state.playerCurrent) {
streak += 1;
if (streak >= 4) {
state.ended = state.playerCurrent;
return;
}
} else {
streak = 0;
}
}
// naive algorithm to check for win condition in the
downward diagonal
// e.g.
// [
// -> rows
// [2, 2, 1, 1, 0, 0], // |
// [2, 1, 1, 0, 0, 0], // v
// [2, 1, 0, 0, 0, 0], //
// [1, 2, 0, 0, 0, 0], // c
// [0, 0, 0, 0, 0, 0], // o
// [0, 0, 0, 0, 0, 0], // l
// [0, 0, 0, 0, 0, 0] // s
// ]
streak = 0;
// iterate through the row
for (ii = 0; ii < state.board.length; ii += 1) {
if (state.board[ii][positionRow - ii + positionCol] ===
state.playerCurrent) {
streak += 1;
if (streak >= 4) {
state.ended = state.playerCurrent;
return;
}
} else {
streak = 0;
}
}
// naive algorithm to check if game ends in a draw
if (state.board.every(function (colList) {
return colList[colList.length - 1] !== 0;
})) {
state.ended = 0;
return;
}
// switch player for next move
state.playerCurrent = state.playerCurrent === 1
? 2
: 1;
};
}());
switch (local.modeJs) {
// run browser js-env code - post-init
case 'browser':
local.domGameBoard = document.querySelector('#gameBoard1');
local.gameDraw = function (state) {
/*
* this function will draw the current state of the game
*/
var board, tmp;
tmp = '';
// game ended with a draw
if (state.ended === 0) {
tmp += 'game is a draw!';
tmp += '<div class="playerDisc"></div>';
} else {
// game ended with a win
if (state.ended) {
tmp += 'player ' + state.playerCurrent + ' has won!';
// game is ongoing
} else {
tmp += 'player ' + state.playerCurrent + '\'s turn';
if (state.error && state.error.message === 'invalid move') {
tmp += ' <span style="color: #f00;">(invalid
move, retry!)</span>';
}
}
tmp += '<div class="playerDisc playerDisc' +
state.playerCurrent + '"></div>';
}
document.querySelector('#gameStatus1').innerHTML = tmp;
// remove error
state.error = null;
// transpose board
board = state.board[0].map(function (_, ii) {
// jslint-hack
local.nop(_);
return state.board.map(function (colList) {
return colList[ii];
});
}).reverse();
board = '<table>\n' +
'<thead>' + board[0].map(function (_, ii) {
// jslint-hack
local.nop(_);
return '<th><button data-position-col="' + ii +
'">▼</button></th>';
}).join('') + '</thead>\n' +
'<tbody>' + board.map(function (rowList) {
return '<tr>' + rowList.map(function (playerDisc) {
return '<td><div class="playerDisc playerDisc' +
playerDisc + '"></div></td>';
}).join('') + '</tr>';
}).join('\n') + '</tbody></table>';
local.domGameBoard.innerHTML = board;
};
local.testRun = function (event) {
switch (event && event.currentTarget.id) {
case 'gameBoard1':
// perform player move
if (event.target.dataset.positionCol) {
local.playerMove(local.gameState,
Number(event.target.dataset.positionCol));
local.gameDraw(local.gameState);
}
break;
// reset game
case 'resetButton1':
local.gameState = local.gameStateCreate();
local.gameDraw(local.gameState);
break;
}
};
// init event-handling
['click'].forEach(function (event) {
Array.prototype.slice.call(
document.querySelectorAll('.on' + event)
).forEach(function (element) {
element.addEventListener(event, local.testRun);
});
});
// reset game
document.querySelector('#resetButton1').click();
break;
// run node js-env code - post-init
case 'node':
// require modules
local.http = require('http');
local.fs = require('fs');
try {
local.utility2 = require('utility2');
} catch (ignore) {
}
// save assets-script
local.assetsScript = local.fs.readFileSync(__filename, 'utf8');
// init server
local.server = local.http.createServer(function (request, response) {
// debug
console.log('handling request ' + request.url);
// serve assets-script
if (request.url.lastIndexOf('test.js') >= 0) {
response.end(local.assetsScript);
return;
}
// serve main-page
/* jslint-ignore-begin */
response.end('\
<html lang="en">\n\
<head>\n\
<meta charset="UTF-8">\n\
<title>connect4</title>\n\
<style>\n\
#gameBoard1 thead button:hover {\n\
cursor: pointer;\n\
}\n\
#gameBoard1 thead {\n\
margin-bottom: 10px;\n\
}\n\
#gameBoard1 table {\n\
background-color: #77f;\n\
}\n\
.playerDisc {\n\
background-color: #fff;\n\
border: 1px solid black;\n\
border-radius: 20px;\n\
height: 20px;\n\
margin: 5px;\n\
width: 20px;\n\
}\n\
.playerDisc1 {\n\
background-color: #ff0;\n\
}\n\
.playerDisc2 {\n\
background-color: #f00;\n\
}\n\
</style>\n\
</head>\n\
<body>\n\
<h1><a href="https://github.com/kaizhu256/node-connect4">connect-4
game</a></h1>\n\
<h4><a download href="test.js">download standalone app</a></h4>\n\
<button class="onclick" id="resetButton1">reset game</button><br>\n\
<br>\n\
<h2 id="gameStatus1"></h2>\n\
<div id="gameContainer1">\n\
<div class="onclick" id="gameBoard1"></div>\n\
</div>\n\
<script src="test.js"></script>\n\
</body>\n\
');
/* jslint-ignore-end */
});
local.server.on('error', function (error) {
if (error.code === 'EADDRINUSE' && !local.EADDRINUSE) {
local.EADDRINUSE = error;
local.PORT = Number(local.PORT) + 1;
local.server.listen(local.PORT, function () {
console.log('server listening on port ' + local.PORT);
});
return;
}
throw error;
});
local.PORT = process.env.PORT || 8081;
local.server.listen(local.PORT, function () {
console.log('server listening on port ' + local.PORT);
});
break;
}
// run shared js-env code - post-init
(function () {
return;
}());
}());
```
On 11/5/17, Raul-Sebastian Mihăilă <raul.mihaila at gmail.com> wrote:
> Also, in the same context, for different mixin associated objects there
> will be different mix objects:
>
> ```js
> function F() {
> const obj = {};
> const obj2 = {};
>
> mixin obj, mixin1;
> mixin obj2, mixin2;
> }
>
> function mixin1() {
> return (obj, mix) => {};
> }
>
> function mixin2() {
> return (obj, mix) => {};
> }
> ```
>
> The mix argument that the mixin function created by mixin2 receives will be
> different from the one received by the mixin function created by mixin1
> because obj1 !== obj2.
>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: connect4.png
Type: image/png
Size: 68772 bytes
Desc: not available
URL: <http://mail.mozilla.org/pipermail/es-discuss/attachments/20171105/2bc4681c/attachment-0001.png>
More information about the es-discuss
mailing list