-
Notifications
You must be signed in to change notification settings - Fork 2
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit c07d62a
Showing
73 changed files
with
373,010 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
node_modules | ||
.DS_STORE | ||
tensorboard_logs | ||
dist | ||
.vscode |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
# How to Contribute | ||
|
||
We are not currently accepting external code contributions to this project. Please feel free to file bugs using GitHub's issue tracker." |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Tic-Tac-Toe the Hard Way | ||
|
||
This repository contains code to accompany the podcast ["Tic-Tac-Toe the Hard Way"](https://pair.withgoogle.com/thehardway). | ||
|
||
The code provided here is offered as is purely for illustrative purposes for particularly adventurous listeners. | ||
There are two authors for the code in this repository: | ||
- David: A writer | ||
- Yannick: A software engineer | ||
|
||
The code is provided with minimal changes or cleanup from how it was originally | ||
written while exploring the topics in the podcast, this is to encourage anyone | ||
just getting started, its okay if your code doesn't look perfect! | ||
|
||
This repository acts primarily as accompanying documentation for the podcast and | ||
will remain as is. | ||
|
||
> We will not be accepting contributions to this project. | ||
## Requirements & Setup | ||
|
||
You will generally need to be comfortable with JavaScript and Node.js to make use | ||
of these repository. If that is not you (and even if it is) we encourage you to | ||
checkout the podcast itself at https://pair.withgoogle.com/thehardway. | ||
|
||
There are READMEs the subfolders with further instructions if you are interested in running the code we wrote: | ||
- tic-tac-toe: Contains all the code for the tic-tac-toe experiment | ||
- `tic-tac-toe/training` contains all the training code. | ||
- `tic-tac-toe/viewer` contains all the viewer code. | ||
- tic-tac-__two__: Contains all the code for the tic-tac-two experiment | ||
- `tic-tac-two/training` contains all the training code. | ||
- `tic-tac-two/viewer` contains all the viewer code. | ||
|
||
Before running any of the code in those subfolders you should run `npm install` or `yarn` in this folder. | ||
|
||
> __This is not an official Google product.__ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
{ | ||
"name": "tic-tac-two", | ||
"version": "1.0.0", | ||
"main": "index.js", | ||
"private": true, | ||
"dependencies": { | ||
"clang-format": "^1.4.0", | ||
"typescript": "3.5.3" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
# Tic-tac-toe experiment | ||
|
||
## Training the models | ||
|
||
### David's agent | ||
|
||
- Install dependencies: run `npm install` | ||
- Generate data and train model: run `npm run generate-and-train` | ||
|
||
This will a new folder in the `models/` directory with the saved model. This output can be used in the [viewer](viewer) | ||
|
||
### Yannick's agent | ||
|
||
- Install dependencies: run `npm install` | ||
- Train model: run `npm run train` | ||
|
||
This will a new folder in the `models/` directory with the saved model. This output can be used in the [viewer](viewer) | ||
|
||
Training will also write out some metrics to a tensorboard logdir. | ||
|
||
Yannick's training code is currently setup to use tfjs-node-gpu. Which requires a | ||
linux machine with CUDA to run. If you want to run this on a machine without CUDA | ||
(e.g. mac or windows), just replace tfjs-node-gpu with tfjs-node wherever you see it. | ||
|
||
## Seeing the results | ||
|
||
The [viewer](viewer) folder contains a simple web app to see the results of the game. To see it, launch a webserver in this folder. e.g. | ||
|
||
``` | ||
python -m SimpleHTTPServer 8000 | ||
``` | ||
|
||
Then go to `localhost:8000/viewer` in your browser. | ||
|
||
If you want to change the model loaded by each agent in the viewer, edit _david_agent.js_ or _yannick_agent.js_ respectively. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,43 @@ | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC. All Rights Reserved. | ||
* 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. | ||
* ============================================================================= | ||
*/ | ||
|
||
export class AbstractAgent { | ||
/** | ||
* Creates an agent that can play | ||
* options is of the form: { symbol: 'x' } | ||
*/ | ||
constructor(options) { | ||
this.symbol = options.symbol; | ||
this.name = options.name || this.constructor.name; | ||
} | ||
|
||
/** | ||
* An initialization function that can be used to load state that may | ||
* take a while to complete. | ||
*/ | ||
async init() { | ||
return; | ||
} | ||
|
||
/** | ||
* Takes a boardState and returns a move. A move is an object | ||
* of the form {symbol: 'x'|'o', position: number} | ||
* @param {Object} boardState | ||
* @returns {symbol: 'x'|'o', position: number} move | ||
*/ | ||
move(boardState) {} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,124 @@ | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC. All Rights Reserved. | ||
* 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. | ||
* ============================================================================= | ||
*/ | ||
|
||
export const BOARD_EMPTY_VAL = -1; | ||
|
||
export function checkForWinner(boardState) { | ||
const rows = 3; | ||
const cols = 3; | ||
const stride = 3; | ||
|
||
function checkCells(boardState, indices) { | ||
const cell1 = boardState[indices[0]]; | ||
const cell2 = boardState[indices[1]]; | ||
const cell3 = boardState[indices[2]]; | ||
|
||
if (cell1 != -1 && cell1 === cell2 && cell2 === cell3) { | ||
return { | ||
hasWinner: true, winningSymbol: cell1, winningCells: indices, | ||
} | ||
} else { | ||
return { | ||
hasWinner: false, | ||
} | ||
} | ||
} | ||
|
||
// Check the rows | ||
for (let i = 0; i < (rows * stride); i += stride) { | ||
const indices = [ | ||
i, | ||
i + 1, | ||
i + 2, | ||
]; | ||
const result = checkCells(boardState, indices); | ||
if (result.hasWinner) { | ||
return result; | ||
} | ||
} | ||
|
||
// Check the columns | ||
for (let i = 0; i < cols; i++) { | ||
const indices = [ | ||
i, | ||
i + stride, | ||
i + (stride * 2), | ||
]; | ||
const result = checkCells(boardState, indices); | ||
if (result.hasWinner) { | ||
return result; | ||
} | ||
} | ||
|
||
// Check Diagonal 1, [0,0], [1,1], [2,2]; | ||
{ | ||
const indices = [ | ||
0, | ||
1 + (1 * stride), | ||
2 + (2 * stride), | ||
]; | ||
const result = checkCells(boardState, indices); | ||
if (result.hasWinner) { | ||
return result; | ||
} | ||
} | ||
|
||
// Check Diagonal 2, [0,2], [1,1], [2,0], | ||
{ | ||
const indices = [ | ||
stride - 1, | ||
1 + (1 * stride), | ||
0 + (2 * stride), | ||
]; | ||
|
||
const result = checkCells(boardState, indices); | ||
if (result.hasWinner) { | ||
return result; | ||
} | ||
} | ||
|
||
return { | ||
hasWinner: false, | ||
}; | ||
} | ||
|
||
export function checkForIllegalMove(boardState, move) { | ||
if (boardState[move.position] !== BOARD_EMPTY_VAL || move.position < 0 || | ||
move.position > 8) { | ||
return { | ||
hasError: true, | ||
errorCell: move.position, | ||
}; | ||
} | ||
|
||
return { | ||
hasError: false, | ||
}; | ||
} | ||
|
||
export function checkDone(boardState) { | ||
const isDone = boardState.every(symbol => symbol != -1); | ||
return isDone; | ||
} | ||
|
||
export async function wait(time) { | ||
return new Promise(resolve => { | ||
setTimeout(() => { | ||
resolve(); | ||
}, time); | ||
}); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
/** | ||
* @license | ||
* Copyright 2020 Google LLC. All Rights Reserved. | ||
* 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. | ||
* ============================================================================= | ||
*/ | ||
|
||
import {AbstractAgent} from './agent.js'; | ||
|
||
export class RandomAgent extends AbstractAgent { | ||
constructor(options) { | ||
super(options); | ||
} | ||
|
||
move(boardState) { | ||
const freeIndices = boardState.reduce((memo, val, idx) => { | ||
if (val === -1) { | ||
memo.push(idx); | ||
} | ||
return memo; | ||
}, []); | ||
|
||
if (freeIndices.length === 0) { | ||
throw new Error('No legal moves'); | ||
} | ||
|
||
// Select an index to play. | ||
const nextIndex = Math.floor(Math.random() * freeIndices.length); | ||
return { | ||
symbol: this.symbol, | ||
position: freeIndices[nextIndex], | ||
}; | ||
} | ||
} |
Oops, something went wrong.