mirror of https://github.com/espruino/BangleApps
Merge pull request #3034 from hughbarney/master
Oxo Focus - play the computer as it learns to play naughts and crossespull/3035/head^2
commit
d574bbda17
|
@ -0,0 +1 @@
|
|||
0.01: first version
|
|
@ -0,0 +1,28 @@
|
|||
# Oxofocus
|
||||
|
||||
A Naughts and Crosses game that learns as it goes
|
||||
|
||||

|
||||

|
||||

|
||||
|
||||
To start with the computer will play random moves and slowly increase
|
||||
its skill based on a set of logic rules. During your first few games
|
||||
the playing logic will apply a new logic rule after each time you
|
||||
win. Once you reach eight wins the banner area will turn green
|
||||
indicating that the computer is now playing at maximum strength.
|
||||
However it is not as easy as you think and if you make a mistake and
|
||||
loose a game your score goes back to zero. The more you play against
|
||||
a weaker algorithm the more likely you are to loose concentration.
|
||||
|
||||
Have you got the focus and concentration to get to maximum playing
|
||||
strength. Do you know all the winning moves to out fox the algorithm
|
||||
?
|
||||
|
||||
Written by: [Hugh Barney](https://github.com/hughbarney) For support
|
||||
and discussion please post in the [Bangle JS
|
||||
Forum](http://forum.espruino.com/microcosms/1424/)
|
||||
|
||||
Credit to `MissionMake` for
|
||||
[tictactoe](https://banglejs.com/apps/?id=tictactoe) where I have
|
||||
borrowed the grid drawing code
|
|
@ -0,0 +1 @@
|
|||
require("heatshrink").decompress(atob("mEwwIJGv//AAX+oEAwEBgECApuD4IFBocww1hAoXwxkxAoNB8GIiIFC4AFDofgCIYdFoEQFIZBRLLoFBHYkAI4YFBKYYFHCIodFLO8M4RHDh4FCKYMHwQFELIkPBYQdFFIVCLK8AAAg="))
|
|
@ -0,0 +1,464 @@
|
|||
|
||||
const YOUR_MOVE = 0;
|
||||
const SHOW_SELECTION = 1;
|
||||
const DISPLAY_MOVE = 2;
|
||||
const THINKING = 3;
|
||||
const GAME_OVER = 4;
|
||||
|
||||
var move_count;
|
||||
var win_count = 0;
|
||||
var game_state;
|
||||
var msg;
|
||||
var board;
|
||||
|
||||
const wins = [ [1,2,3], [4,5,6], [7,8,9], [1,4,7], [2,5,8], [3,6,9], [1,5,9], [3,5,7] ];
|
||||
const rowcol = [ [-1,-1], [1,1], [1,2], [1,3], [2,1], [2,2], [2,3], [3,1], [3,2], [3,3] ];
|
||||
const x_img = require("heatshrink").decompress(atob("mEwwI63jACEngCEvwCEv4CB/wCBn+AgP8AoMf4ED/AFBh/gg/wAoIDBA4IFBB4ITBAoIbBD4I8C/wrCGAQuCGAQuCGAQuCGAQuCAo4RFDoopFGohBFJopZFMopxFPoqJFSoqhFVooA0A"));
|
||||
const o_img = require("heatshrink").decompress(atob("mEwwIdah/wAof//4ECgYFB4AFBg4FB8AFBj/wh/4AoM/wEB/gFBvwCB/wCBBAU/AQIUCj8AgIzCh+AgYmCg/AgYyCAYIHBAoXgg+AAoMBApkPLgZKBAtBBRLIprDMoJxFPoqJFSoyhCAQStFXIrFFaIrdFdIwAVA"));
|
||||
|
||||
|
||||
function debug(o) {
|
||||
//console.log(o);
|
||||
}
|
||||
|
||||
|
||||
// 1 2 3
|
||||
// 4 5 6
|
||||
// 7 8 9
|
||||
|
||||
function draw(){
|
||||
debug("draw()");
|
||||
g.clear();
|
||||
message(msg);
|
||||
|
||||
//drawboard
|
||||
g.setColor(g.theme.fg);
|
||||
g.drawLine(62,24,62,176);
|
||||
g.drawLine(112,24,112,176);
|
||||
g.drawLine(12,74,164,74);
|
||||
g.drawLine(12,124,164,124);
|
||||
|
||||
for (let cell = 1; cell < 10; cell++) {
|
||||
let row = rowcol[cell][0];
|
||||
let col = rowcol[cell][1];
|
||||
|
||||
if (board[cell] == "X") {
|
||||
g.drawImage(x_img, (col - 1)*50+12, (row - 1)*50+24);
|
||||
} else if (board[cell] == "O") {
|
||||
g.drawImage(o_img, (col - 1)*50+12, (row - 1)*50+24);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function message(m) {
|
||||
g.reset();
|
||||
// if all rules are operating show a green background
|
||||
debug('win count=' + win_count);
|
||||
|
||||
if (win_count == 0) {
|
||||
g.setColor('#f00'); // red, no wins
|
||||
} else if (win_count < 8) {
|
||||
g.setColor('#00f'); // blue, some wins, not all rules active
|
||||
} else {
|
||||
g.setColor('#0f0'); // green all rules active
|
||||
}
|
||||
|
||||
g.fillRect(0, 0, 176, 23);
|
||||
g.setColor('#fff');
|
||||
g.setFont('6x8',2);
|
||||
g.setFontAlign(0, 0);
|
||||
g.drawString("" + win_count + " " + m, g.getWidth()/2, 12);
|
||||
}
|
||||
|
||||
|
||||
// Square locations
|
||||
//12,24;62,24,112,24
|
||||
//12,74;62,74,112,74
|
||||
//12,124;62,124,112,124
|
||||
|
||||
function get_move() {
|
||||
var col;
|
||||
var row;
|
||||
|
||||
if (game_state != YOUR_MOVE)
|
||||
return;
|
||||
|
||||
// work out which row/col was selected
|
||||
if (x <= 62) {
|
||||
col= 0;
|
||||
} else if (x <= 112){
|
||||
col= 1;
|
||||
} else {
|
||||
col= 2;
|
||||
}
|
||||
|
||||
if (y <= 74) {
|
||||
row = 0;
|
||||
} else if (y <= 124){
|
||||
row = 1;
|
||||
} else {
|
||||
row = 2;
|
||||
}
|
||||
|
||||
// convert row / col to a cell
|
||||
let cell = 3*row + col + 1;
|
||||
debug("select:" + cell);
|
||||
|
||||
if (cell_is_free(cell)) {
|
||||
set_cell(cell,'X');
|
||||
move_count++;
|
||||
game_state = SHOW_SELECTION;
|
||||
if (check_for_win()) {
|
||||
draw();
|
||||
return;
|
||||
}
|
||||
next_state();
|
||||
} else {
|
||||
message('try again');
|
||||
}
|
||||
}
|
||||
|
||||
function new_game() {
|
||||
game_state = YOUR_MOVE;
|
||||
move_count = 0;
|
||||
msg = 'your move';
|
||||
board = [ "-", "1", "2", "3", "4", "5", "6", "7", "8", "9" ];
|
||||
draw();
|
||||
}
|
||||
|
||||
function next_state() {
|
||||
debug("state=" + game_state);
|
||||
|
||||
// show humans selected move with a selection circle
|
||||
if (game_state == SHOW_SELECTION) {
|
||||
game_state = DISPLAY_MOVE;
|
||||
//message('selection..');
|
||||
g.fillCircle(x, y, 10);
|
||||
setTimeout(next_state,300);
|
||||
} else if (game_state == DISPLAY_MOVE) {
|
||||
game_state = THINKING;
|
||||
msg = 'thinking..';
|
||||
draw();
|
||||
setTimeout(next_state,1800);
|
||||
} else if (game_state == THINKING) {
|
||||
game_state = YOUR_MOVE;
|
||||
msg = 'your move';
|
||||
computer_move();
|
||||
move_count++;
|
||||
check_for_win();
|
||||
draw();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
function computer_move() {
|
||||
var mvs;
|
||||
var mv;
|
||||
|
||||
if (win_count > 0) {
|
||||
if (first_move_was_a_corner()) {
|
||||
make_my_move(5);
|
||||
debug("RULE 1: you played corner, computer played centre");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 1) {
|
||||
if (first_move_was_the_centre()) {
|
||||
mv = get_a_corner_move();
|
||||
make_my_move(mv);
|
||||
debug("RULE 2: you played center, computer played corner");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 6) {
|
||||
if (first_move_was_a_side()) {
|
||||
make_my_move(5);
|
||||
debug("RULE 3: you played side, computer played centre");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 2) {
|
||||
mvs = get_winning_moves("O");
|
||||
if (mvs.length > 0) {
|
||||
mv = select_random_move_from(mvs);
|
||||
make_my_move(mv);
|
||||
debug("RULE 4: computer played a winning move");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 3) {
|
||||
mvs = get_winning_moves("X");
|
||||
if (mvs.length > 0) {
|
||||
mv = select_random_move_from(mvs);
|
||||
make_my_move(mv);
|
||||
debug("RULE 5: computer played a blocking move");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/***
|
||||
Adjacent Sides, play in appropriate corner (.)
|
||||
|
||||
. | X
|
||||
---|---
|
||||
X |
|
||||
|
||||
***/
|
||||
|
||||
// not covered by rule 3
|
||||
if (win_count > 4) {
|
||||
if (player_adjacent_sides("X")) {
|
||||
mv = get_adjacent_corner("X");
|
||||
if (mv != -1) {
|
||||
make_my_move(mv);
|
||||
debug("RULE 6: compluter played adjacent corner");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 7) {
|
||||
if (player_has_corner_and_centre("X")) {
|
||||
mv = get_a_corner_move();
|
||||
if (mv != -1) {
|
||||
make_my_move(mv);
|
||||
debug("RULE 7: compluter played a corner");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (win_count > 5) {
|
||||
mvs = get_free_sides();
|
||||
if (mvs.length > 0) {
|
||||
mv = select_random_move_from(mvs);
|
||||
make_my_move(mv);
|
||||
debug("RULE 8: compluter played a side");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// default rule
|
||||
mvs = get_free_cells();
|
||||
mv = select_random_move_from(mvs);
|
||||
debug("RULE 8: computer played a random cell");
|
||||
make_my_move(mv);
|
||||
}
|
||||
|
||||
// check the move and make it for "O"
|
||||
function make_my_move(mv) {
|
||||
if (valid_move(mv)) {
|
||||
set_cell(mv, "O");
|
||||
} else {
|
||||
debug("make_my_move(): Invalid move was generated " + mv);
|
||||
}
|
||||
}
|
||||
|
||||
function check_for_win() {
|
||||
if (player_has_won("X")) {
|
||||
msg = 'you win';
|
||||
game_state = GAME_OVER;
|
||||
win_count++;
|
||||
return true;
|
||||
} else if (player_has_won("O")) {
|
||||
msg = 'I win';
|
||||
win_count = 0;
|
||||
game_state = GAME_OVER;
|
||||
return true;
|
||||
} else if (check_for_draw()) {
|
||||
msg = 'draw';
|
||||
game_state = GAME_OVER;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function player_has_won(player) {
|
||||
for (var r in wins)
|
||||
if (row_is_won(wins[r], player))
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function check_for_draw() {
|
||||
var v = get_free_cells();
|
||||
return (v.length == 0);
|
||||
}
|
||||
|
||||
function row_is_won(rw, pl) {
|
||||
if (board[rw[0]] == pl && board[rw[1]] == pl && board[rw[2]] == pl)
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
function get_winning_moves(player) {
|
||||
var win_moves = new Array();
|
||||
|
||||
for (var r in wins) {
|
||||
var ind = winning_move_for_row(wins[r], player);
|
||||
|
||||
if (ind > -1) {
|
||||
win_moves.push(wins[r][ind]);
|
||||
}
|
||||
}
|
||||
|
||||
return win_moves;
|
||||
}
|
||||
|
||||
|
||||
function winning_move_for_row(rw, pl) {
|
||||
if (board[rw[1]] == pl && board[rw[2]] == pl && cell_is_free(rw[0])) return 0;
|
||||
if (board[rw[2]] == pl && board[rw[0]] == pl && cell_is_free(rw[1])) return 1;
|
||||
if (board[rw[0]] == pl && board[rw[1]] == pl && cell_is_free(rw[2])) return 2;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function first_move_was_a_corner() {
|
||||
if (move_count != 1)
|
||||
return false;
|
||||
|
||||
if (board[1] == "X" || board[3] == "X" || board[7] == "X" || board[9] == "X")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function first_move_was_a_side() {
|
||||
if (move_count != 1)
|
||||
return false;
|
||||
|
||||
if (board[2] == "X" || board[4] == "X" || board[6] == "X" || board[8] == "X")
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function player_adjacent_sides(pl) {
|
||||
if (move_count > 3) return false;
|
||||
if (board[2] == pl && board[4] == pl) return true;
|
||||
if (board[2] == pl && board[6] == pl) return true;
|
||||
if (board[8] == pl && board[2] == pl) return true;
|
||||
if (board[8] == pl && board[6] == pl) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_adjacent_corner(pl) {
|
||||
if (board[2] == pl && board[4] == pl) return 1;
|
||||
if (board[2] == pl && board[6] == pl) return 3;
|
||||
if (board[8] == pl && board[2] == pl) return 7;
|
||||
if (board[8] == pl && board[6] == pl) return 9;
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
function player_has_corner_and_centre(pl) {
|
||||
if (board[1] == pl && board[5] == pl) return true;
|
||||
if (board[3] == pl && board[5] == pl) return true;
|
||||
if (board[7] == pl && board[5] == pl) return true;
|
||||
if (board[9] == pl && board[5] == pl) return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
function first_move_was_the_centre() {
|
||||
if (move_count == 1 && board[5] == "X")
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function get_a_side_move() {
|
||||
return select_random_move_from([2,4,6,8]);
|
||||
}
|
||||
|
||||
function get_a_corner_move() {
|
||||
return select_random_move_from([1,3,7,9]);
|
||||
}
|
||||
|
||||
function select_random_move_from(mvs) {
|
||||
var len = mvs.length;
|
||||
var rnd = random(len) - 1;
|
||||
return mvs[rnd];
|
||||
}
|
||||
|
||||
function random(n) {
|
||||
try {
|
||||
return Math.floor((Math.random() * n) + 1);
|
||||
} catch ( e ) { debug("Error: " + this + e.description); }
|
||||
}
|
||||
|
||||
function get_free_cells() {
|
||||
var frees = new Array();
|
||||
|
||||
for (var i in board) {
|
||||
if (i > 0 && cell_is_free(i))
|
||||
frees.push(i);
|
||||
}
|
||||
return frees;
|
||||
}
|
||||
|
||||
function get_free_sides() {
|
||||
var frees = new Array();
|
||||
var sides = [2,4,6,8];
|
||||
|
||||
for (var i in sides) {
|
||||
if (cell_is_free(sides[i]))
|
||||
frees.push(sides[i]);
|
||||
}
|
||||
return frees;
|
||||
}
|
||||
|
||||
function get_free_corner() {
|
||||
var frees = new Array();
|
||||
var sides = [1,3,7,9];
|
||||
|
||||
for (var i in sides) {
|
||||
if (cell_is_free(sides[i]))
|
||||
frees.push(sides[i]);
|
||||
}
|
||||
return frees;
|
||||
}
|
||||
|
||||
function cell_is_free(i) {
|
||||
if (board[i] == "X" || board[i] == "O") return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function valid_move(id) {
|
||||
if (cell_is_free(id) == false) {
|
||||
debug("Invalid move, try another cell");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function set_cell(n, player) {
|
||||
if (player == "X") {
|
||||
board[n] = "X";
|
||||
} else {
|
||||
board[n] = "O";
|
||||
}
|
||||
}
|
||||
|
||||
Bangle.on('touch', function(zone,e) {
|
||||
x = Object.values(e)[0];
|
||||
y = Object.values(e)[1];
|
||||
|
||||
if (game_state == GAME_OVER) {
|
||||
new_game();
|
||||
return;
|
||||
}
|
||||
|
||||
get_move();
|
||||
});
|
||||
|
||||
|
||||
new_game();
|
Binary file not shown.
After Width: | Height: | Size: 594 B |
|
@ -0,0 +1,19 @@
|
|||
{ "id": "oxofocus",
|
||||
"name": "oxofocus",
|
||||
"shortName":"Oxo Focus",
|
||||
"icon": "app.png",
|
||||
"version":"0.01",
|
||||
"description": "Play the computer while it learns to play Naughts and Crosses!",
|
||||
"readme": "README.md",
|
||||
"tags": "game",
|
||||
"storage": [
|
||||
{"name":"oxofocus.app.js","url":"app.js"},
|
||||
{"name":"oxofocus.img","url":"app-icon.js","evaluate":true}
|
||||
],
|
||||
"screenshots" : [
|
||||
{ "url":"screenshot.png" },
|
||||
{ "url":"screenshot1.png" },
|
||||
{ "url":"screenshot2.png" }
|
||||
],
|
||||
"supports": ["BANGLEJS2"]
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
After Width: | Height: | Size: 3.2 KiB |
Loading…
Reference in New Issue