So I made up this game, but I'm not able to find a good strategy to always win.
It's very similar to the original 3×3 Tic-Tac-Toe; but with changes.
So you draw a 5×5 board. Then each player takes turns putting a cross and circle. We then count the "scores".
This is a completed game that I drew. The scores are made by counting every 3-in-a-row strike. So for example in the top row, cross player gets 1 point.
You then do the counting horizontally, vertically, and both ways diagonally; for each row, column and diagonal.
For a 4-in-a-row, you get two points, as you can look at it as 2 different 3-in-a-rows. Similarly, a 5-in-a-row would get 3 points.
In the example game, cross wins as it get 9 whereas circle gets only 7.
I play this a lot; but it's always hard to tell where to put your next move. I've noticed that starting first gives you a significant advantage. To compensate for this, I lower the points of the player who started first by one.
Is it possible to brute force a computer to learn the best move for every game?
Thanks in advance!
Side note: If someone can program this as a simple game with random computer moves, that'd be great. I'm just getting into programming and I'm having a hard time figuring how to do it.
Brute forced
The snippet below will brute force all end games for player X and player O
There are 33,542,145 permutations to test of which ~ 5,000,000 are valid end games. To prevent the page locking up it splits the task into groups of 250,000 permutations
Best score for X is 16 and for O is 14. Run the snippet to see example end game layout and other details.
I did not include any handicap.
This can only do end games as it uses bit fields to keep performance high. However bit fields have no room of empty board positions.
It can be optimized to do both players at the same time by inverting the bits for each game permutation
const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) : el.textContent = data.toString();
const scores = {
[0b00111] : 1,
[0b01110] : 1,
[0b11100] : 1,
[0b11101] : 1,
[0b10111] : 1,
[0b01111] : 2,
[0b11110] : 2,
[0b11111] : 3,
};
const rotateMat = [
0, 5, 10, 15, 20,
1, 6, 11, 16, 21,
2, 7, 12, 17, 22,
3, 8, 13, 18, 23,
4, 9, 14, 19, 24
];
const diagonMat = [
2, 8, 14, -1, -1,
1, 7, 13, 19, -1,
0, 6, 12, 18, 24,
5, 11, 17, 23, -1,
10, 16, 22, -1, -1,
];
const diagonMatUp = [
10, 6, 2, -1, -1,
15, 11, 7, 3, -1,
20, 16, 12, 8, 4,
21, 17, 13, 9, -1,
22, 18, 14,-1, -1,
];
function transform(board, matrix) {
var i = 25, b = 0;
while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
return b;
}
function scoreLines(board) {
var i = 5, score = 0, l = 0;
while (i--) { score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }
return score;
}
function score(board) {
return scoreLines(board) +
scoreLines(transform(board, rotateMat)) +
scoreLines(transform(board, diagonMat)) +
scoreLines(transform(board, diagonMatUp));
}
function isValidGame(board, side) {
var c = 0, i = 25, bits = side + 1;
while (i-- && c < bits) { (board & (1 << i)) && c++ }
return c === side;
}
function showBoard(board, score, side) {
var i = 5;
log(games, "------------------------");
log(games, "End game score: " + score + " player: " + (side===13 ? "X" : "O"));
log(games, "Example end game");
while (i--) {
const line = ((board >> (i * 5)) & 0b11111).toString(2).padStart(5, "0");
const lined = side === 13 ?
line.replace(/1/g,"X").replace(/0/g,"O") :
line.replace(/1/g,"O").replace(/0/g,"X");
log(games, lined);
}
}
function brute(side = 13) {
function doSet(i) {
var ii = 251357;
while (i >= min && ii--) {
if (isValidGame(i, side)) {
gameCount ++;
const s = score(i);
if (s >= maxScore) {
if (s > maxScore) {
bestEndGames.length = 0
game = i;
}
maxScore = s;
bestEndGames.push(i);
}
}
i--;
}
if (i >= min) {
setTimeout(doSet, 8, i);
log(progress, status + " is: " + maxScore + " tested: " + ((max - min) - (i - min)) + " of " + (max - min) + " permutations", false);
} else {
log(games, status + " is: " + maxScore + " of " + gameCount + " end games");
log(games, "Number of end games with best score: " + bestEndGames.length);
showBoard(game, maxScore, side);
if (side === 13) { setTimeout(brute, 1000, 12) }
else { log(progress, "Done", false) }
}
}
var game, gameCount = 0;
var maxScore = 0;
const bestEndGames = [];
const status = "Best score player: " + (side===13 ? "X" : "O");
const [min, max] = side === 13 ? [0b1111111111111, 0b1111111111111000000000000] : [0b111111111111, 0b1111111111110000000000000];
doSet(max)
}
brute(13);
<div id="progress"></div>
<div id="games"></div>
To score a game the next snippet will do so. A bit of a hack, enter game into input fields and will spit out scores.
const tag = (tag, props = {}) => Object.assign(document.createElement(tag), props);
const append = (par, ...sibs) => sibs.reduce((p, sib) => (p.appendChild(sib), p), par);
const log = (el, data, add = true) => add ? append(el, tag("div", {textContent: data.toString()})) : el.textContent = data.toString();
const scores = {
[0b00111] : 1,
[0b01110] : 1,
[0b11100] : 1,
[0b11101] : 1,
[0b10111] : 1,
[0b01111] : 2,
[0b11110] : 2,
[0b11111] : 3,
};
const rotateMat = [
0, 5, 10, 15, 20,
1, 6, 11, 16, 21,
2, 7, 12, 17, 22,
3, 8, 13, 18, 23,
4, 9, 14, 19, 24
];
const diagonMat = [
2, 8, 14, -1, -1,
1, 7, 13, 19, -1,
0, 6, 12, 18, 24,
5, 11, 17, 23, -1,
10, 16, 22, -1, -1,
];
const diagonMatUp = [
10, 6, 2, -1, -1,
15, 11, 7, 3, -1,
20, 16, 12, 8, 4,
21, 17, 13, 9, -1,
22, 18, 14,-1, -1,
];
function transform(board, matrix) {
var i = 25, b = 0;
while (i--) { matrix[i] > -1 && (board & (1 << matrix[i])) && (b |= 1 << i) }
return b;
}
function scoreLines(board) {
var i = 5, score = 0, l = 0;
while (i--) { score += scores[(board >> (i * 5)) & 0b11111] ?? 0 }
return score;
}
function score(board) {
return scoreLines(board) +
scoreLines(transform(board, rotateMat)) +
scoreLines(transform(board, diagonMat)) +
scoreLines(transform(board, diagonMatUp));
}
function isValidGame(board, side, asStr) {
var c = 0, i = 25, bits = side + 1;
while (i-- && c < bits) { (
(asStr[24-i] !== "-" && asStr[24-i] !== " ") && board & (1 << i)) && c++
}
return [c <= side, c];
}
function showBoard(board, asStr) {
var i = 0;
var j = 0;
while (i < 5) {
const line = ((board >> ((4-i) * 5)) & 0b11111).toString(2).padStart(5, "0");
const lined = line.replace(/1/g,"X").replace(/0/g,"O");
var str = "";
j = 0;
while (j < 5) {
str += (asStr[i * 5 + j] !== "-" && asStr[i * 5 + j] !== " ") ? lined[j] : ".";
j++;
}
log(games, str);
i++;
}
}
gameVal1.addEventListener("input", testGame);
gameVal2.addEventListener("input", testGame);
gameVal3.addEventListener("input", testGame);
gameVal4.addEventListener("input", testGame);
gameVal5.addEventListener("input", testGame);
function testGame() {
var board = gameVal1.value.slice(0,5).padEnd(5, "-");
board += gameVal2.value.slice(0,5).padEnd(5, "-");
board += gameVal3.value.slice(0,5).padEnd(5, "-");
board += gameVal4.value.slice(0,5).padEnd(5, "-");
board += gameVal5.value.slice(0,5).padEnd(5, "-");
board = board.replace(/[^OX\- ]/gi,"-");
const X = eval("0B" + board.replace(/X/gi,"1").replace(/O|-| /gi,"0"));
const O = eval("0B" + board.replace(/X|-| /gi,"0").replace(/O/gi,"1"));
const [vx, movesX] = isValidGame(X, 13, board);
const [vo, movesY] = isValidGame(O, 12, board);
vx && log(resultX, "Player X score: " + score(X) + " moves: " + movesX, false);
vo && log(resultO, "Player O score: " + score(O) + " moves: " + movesY, false);
games.innerHTML = "";
showBoard(X, board);
}
testGame()
input {
font-family: monospace;
font-size: large;
}
.fixFont {
font-family: monospace;
font-size: large;
}
#games {
position: absolute;
top: 28px;
left: 80px;
border: 1px solid black;
padding: 0px 3px;
letter-spacing: 3px;
}
#resultX {
position: absolute;
top: 40px;
left: 160px;
}
#resultO {
position: absolute;
top: 60px;
left: 160px;
}
<div class="fixFont">
Enter "x" "x" "O" or "o". Empty slots "-" or space<br>
<input type="text" id="gameVal1" value="XXXXX" size="5"><br>
<input type="text" id="gameVal2" value="XXXXO" size="5"><br>
<input type="text" id="gameVal3" value="XXXXO" size="5"><br>
<input type="text" id="gameVal4" value="OOOOO" size="5"><br>
<input type="text" id="gameVal5" value="OOOOO" size="5"><br>
<div id="resultX"></div>
<div id="resultO"></div>
<div id="games"></div>
</div>
With Google Apps Script I created a stacked bar chart. This is the result:
https://drive.google.com/file/d/1DZ2ZtSu2A81OAMc9ds9A4y-bS0l_oftL/view?usp=sharing
I would like to hide the labels on the bars when they are too wide compared to the available space. Following the instructions I found at this address https://developers.google.com/chart/interactive/docs/reference#DataView_setColumns I tried to use a custom function instead of "stringify" in the "annotationObj" object ( see the code) to create a label of zero length, but my function is not recognized when I try to create the chart (error message: Unknown function "getValueAt").
This is my code:
function CHARTS_002() { //
var ABCarray = ['A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P','Q','R','S','T','U','V','W','X','Y','Z','AA','AB','AC','AD','AE','AF','AG','AH','AI','AJ','AK','AL','AM','AN','AO','AP','AQ','AR','AS','AT','AU','AV','AW','AX','AY','AZ','BA','BB','BC','BD','BE','BF','BG','BH','BI','BJ','BK','BL','BM','BN','BO','BP','BQ','BR','BS','BT','BU','BV','BW','BX','BY','BZ','CA','CB','CC','CD','CE','CF','CG','CH','CI','CJ','CK','CL','CM','CN','CO','CP','CQ','CR','CS','CT','CU','CV','CW','CX','CY','CZ'];
var ssId = '1KA2BnUsC-Lp64UhxjtN5Gtth2dOiHp3-pRwIQjAYOLI';
var shName = 'TopGrossingFilms';
var aScale = ["Poco d'accordo","Né d’accordo né in disaccordo","Abbastanza d'accordo","Totalmente d'accordo","Media"];
var aRange = [['B',2],['N',12]];
var sheet = SpreadsheetApp.openById(ssId).getSheetByName(shName);
var row = aRange[0][1];
var column = ABCarray.indexOf(aRange[0][0]) + 1;
var numRows = aRange[1][1] - aRange[0][1];
var numColumns = ABCarray.indexOf(aRange[1][0]) - ABCarray.indexOf(aRange[0][0]) + 1;
var sheetV = sheet.getRange(aRange[0][1], ABCarray.indexOf(aRange[0][0]) + 1, numRows, numColumns).getValues();
var sheetT1D = sheetV[0];
var aData = [];
for (var r in sheetV) {
aData.push(sheetV[r])
}
for (var r in aData) {
for (var c in aData[r]) {
if (!isNaN(aData[r][c])) {
aData[r][c] = round(aData[r][c],2);
if (aData[0][c] == 'Media') {
aData[r][c] = 13;
}
}
}
}
var data = Charts.newDataTable();
var annotationObj = { calc: "stringify",
//calc: "getValueAt",
//calc: "function(data, row) { var ret = data[row][§col§]; if (ret < 7) {return '';} else {return JSON.stringify(ret)} }",
sourceColumn: -1,
type: "string",
role: "annotation"
}
var aAnnotation = [];
for (var r in aData) {
if (r < 1) { continue; }
if (r == 1) {
for (var c in aData[r]) {
aAnnotation.push(c);
if (isNaN(aData[r][c])) {
data.addColumn(Charts.ColumnType.STRING, aData[0][c]);
} else {
data.addColumn(Charts.ColumnType.NUMBER, aData[0][c]);
if (aScale.indexOf(aData[0][c]) != -1) {
var myObj = JSON.parse(JSON.stringify(annotationObj));
var myCol = Number(c);
if (aData[0][c] == 'Media') {
myCol = Number(c) + 1;
}
myObj.sourceColumn = myCol;
myObj.calc = myObj.calc.replace("§col§",myCol)
aAnnotation.push(myObj);
}
}
}
}
data.addRow(aData[r]);
}
var dataViewDefinition = Charts.newDataViewDefinition().setColumns(aAnnotation);
var aTicks = [0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100];
var chartBuilder = Charts.newBarChart()
.setDimensions(1200, 700)
.setOption("hAxis", { ticks: aTicks})
.setOption('legend',{ position:'top', maxLines:3 })
.setOption('chartArea',{ left:650 })
.setOption('series',{
6: {type:'line', color:'00FF00', lineWidth:3, visibleInLegend: false}
})
.setDataTable(data)
.setDataViewDefinition(dataViewDefinition)
.setOption('bar', { groupWidth: "80%" })
.setStacked()
.setColors(['#C10000','#F1C12A','#BFBFBF','#0070C1','#244062','00FF00']);
var chart = chartBuilder.build();
var chartImage = chart.getAs('image/png').copyBlob();
DriveApp.createFile(chartImage).setName('NewBarChart.png');
}
function getValueAt(column, dataTable, row) {
var value = dataTable(row, column);
var ret = '';
if (value > 7) { ret = value.toString()}
return ret;
}
function round(value, exp) {
var funcName = 'round';
if (typeof exp === 'undefined' || +exp === 0)
return Math.round(value);
value = +value;
exp = +exp;
if (isNaN(value) || !(typeof exp === 'number' && exp % 1 === 0))
return NaN;
// Shift
value = value.toString().split('e');
value = Math.round(+(value[0] + 'e' + (value[1] ? (+value[1] + exp) : exp)));
// Shift back
value = value.toString().split('e');
return +(value[0] + 'e' + (value[1] ? (+value[1] - exp) : -exp));
}
The chart that is produced can be seen in this public folder: https://drive.google.com/file/d/1DZ2ZtSu2A81OAMc9ds9A4y-bS0l_oftL/view?usp=sharing
Does anyone know how to get the result I would like to get?
Thanks in advance.
Hi I have a json from http request, the server json response is this:
{
"map": {
"03/04": 13,
"05/04": 41,
"06/04": 1,
"12/04": 4,
"14/04": 7,
"18/04": 8,
"19/04": 2,
"22/04": 1,
"25/04": 4
},
"links": []
}
I want to split dates in 1 array and values in other array,
At the end I want :
Data[03/04,05/04,06/04....] and
Val[13,41,1....]
is it possible without difficult implementation?
This could be an approach:
private Data = [];
private Val = [];
for (let key in data) {
this.Data.push(key);
this.Val.push(data[key])
}
let date = Object.keys(JsonRespond.map) // get all keys in map object
let value = [];
date.forEach((key) => {
value.push(JsonRespond.map[key]);
})
you can use this
var a=`{
"map": {
"03/04": 13,
"05/04": 41,
"06/04": 1,
"12/04": 4,
"14/04": 7,
"18/04": 8,
"19/04": 2,
"22/04": 1,
"25/04": 4
},
"links": []
}`
var Data=[];
var val=[]
for(each in a.map){
Data.push(each);
val.push(a.map[each]);
}
Use Object.entries
var dates = [];
var values = [];
var data = Object.entries(yourObj.map);
for (var i in data.length) {
dates.push(data[i][0]);
values.push(data[i][1]);
}
I found the following code here only I kept the same in my Adobe Flex project but after clicking on the export to Excel button I can't see any Excel opening in addition to that my datagrid is filling with the column names which I intended to give to my Excel.
public function roExport_export_Result(e:ResultEvent):void
{
if(e.result.length != 0)
{
btnExportToExcel.enabled = true;
var arrExportResult:Array = e.result as Array;
xlsFile = new ExcelFile();
var sheet:Sheet = new Sheet();
sheet.resize(arrExportResult.length+1,14);
sheet.setCell(0,0,'Id');
sheet.setCell(0,1,'Full Name');
sheet.setCell(0,2,'Gender');
sheet.setCell(0,3,'Birth Date');
sheet.setCell(0,4,'College Name');
sheet.setCell(0,5,'Qualification');
sheet.setCell(0,6,'Email Id');
sheet.setCell(0,7,'Mobile');
sheet.setCell(0,8,'Position Applied For');
sheet.setCell(0,9,'Technology Interested');
sheet.setCell(0,10,'User Name');
sheet.setCell(0,11,'Password');
sheet.setCell(0,12,'Exam Date');
sheet.setCell(0,13,'Percentage');
sheet.setCell(0,14,'IsActive');
for(var i:int=0;i<arrExportResult.length;i++)
{
sheet.setCell(i+1, 0, arrExportResult[i].Id);
sheet.setCell(i+1, 1, arrExportResult[i].FullName);
if(arrExportResult[i].Gender == 1)
{
arrExportResult[i].Gender = "Male"
}
else
{
arrExportResult[i].Gender = "Female";
}
sheet.setCell(i+1, 2, arrExportResult[i].Gender);
var date:String = arrExportResult[i].BirthDate.date.toString();
var month:String = (arrExportResult[i].BirthDate.month + 1).toString();
var year:String = arrExportResult[i].BirthDate.fullYear.toString();
var bDate:String = date + "/" + month + "/" + year;
arrExportResult[i].BirthDate = bDate;
sheet.setCell(i+1, 3, arrExportResult[i].BirthDate);
sheet.setCell(i+1, 4, arrExportResult[i].CollegeId);
sheet.setCell(i+1, 5, arrExportResult[i].QualificationId);
sheet.setCell(i+1, 6, arrExportResult[i].EmailId);
sheet.setCell(i+1, 7, arrExportResult[i].Mobile);
sheet.setCell(i+1, 8, arrExportResult[i].PositionName);
sheet.setCell(i+1, 9, arrExportResult[i].TechForTraining);
sheet.setCell(i+1, 10, arrExportResult[i].UserName);
sheet.setCell(i+1, 11, arrExportResult[i].Password);
var date:String = arrExportResult[i].CreatedDate.date.toString();
var month:String = (arrExportResult[i].CreatedDate.month + 1).toString();
var year:String = arrExportResult[i].CreatedDate.fullYear.toString();
var hour:String = arrExportResult[i].CreatedDate.hours.toString();
var min:String = arrExportResult[i].CreatedDate.minutes.toString();
var sec:String = arrExportResult[i].CreatedDate.seconds.toString();
var cDate:String = date + "/" + month + "/" + year + " " + hour + ":" + min + ":" + sec;
arrExportResult[i].CreatedDate = cDate;
sheet.setCell(i+1, 12, arrExportResult[i].CreatedDate);
sheet.setCell(i+1, 13, arrExportResult[i].Percentage);
sheet.setCell(i+1, 14, arrExportResult[i].IsActive);
}
dataGridResult.dataProvider = arrExportResult;
xlsFile.sheets.addItem(sheet);
bytes = xlsFile.saveToByteArray();
}
else
{
arrExportResult = new Array();
dataGridResult.dataProvider = arrExportResult;
btnExportToExcel.enabled = false;
xlsFile = new ExcelFile();
var sheet:Sheet = new Sheet();
Alert.show("No Records Found",parentApplication.alertTitle);
}
}
After you get the byte Array you have to use a FileReference.save(bytes); call to pop up the window that prompts the user for where to save the data... as Flextras/Jeffry says the data grid is probably changing due to the dataGridResult.dataProvider line. If you're doing this as an AIR based desktop app you can use the File and FileStream class to write out the bytes directly without prompting the user.
So long as you have FP 10+ targetted
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/net/FileReference.html#save()
For AIR:
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/com/adobe/livecycle/content/File.html
http://help.adobe.com/en_US/FlashPlatform/reference/actionscript/3/flash/filesystem/FileStream.html#writeBytes()