From 7c8c340c4c8de21343342751d1fd9ecda3001a85 Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Thu, 28 Sep 2023 18:34:22 +0100 Subject: [PATCH 1/5] Oxofocus - learns as it plays --- apps/oxofocus/README.md | 24 ++ apps/oxofocus/app-icon.js | 1 + apps/oxofocus/app.js | 464 ++++++++++++++++++++++++++++++++++ apps/oxofocus/app.png | Bin 0 -> 594 bytes apps/oxofocus/metadata.json | 18 ++ apps/oxofocus/screenshot.png | Bin 0 -> 3282 bytes apps/oxofocus/screenshot1.png | Bin 0 -> 3425 bytes apps/oxofocus/screenshot2.png | Bin 0 -> 3306 bytes 8 files changed, 507 insertions(+) create mode 100644 apps/oxofocus/README.md create mode 100644 apps/oxofocus/app-icon.js create mode 100644 apps/oxofocus/app.js create mode 100644 apps/oxofocus/app.png create mode 100644 apps/oxofocus/metadata.json create mode 100644 apps/oxofocus/screenshot.png create mode 100644 apps/oxofocus/screenshot1.png create mode 100644 apps/oxofocus/screenshot2.png diff --git a/apps/oxofocus/README.md b/apps/oxofocus/README.md new file mode 100644 index 000000000..f65d0f9ca --- /dev/null +++ b/apps/oxofocus/README.md @@ -0,0 +1,24 @@ +# 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 diff --git a/apps/oxofocus/app-icon.js b/apps/oxofocus/app-icon.js new file mode 100644 index 000000000..c238afa61 --- /dev/null +++ b/apps/oxofocus/app-icon.js @@ -0,0 +1 @@ +require("heatshrink").decompress(atob("mEwwIJGv//AAX+oEAwEBgECApuD4IFBocww1hAoXwxkxAoNB8GIiIFC4AFDofgCIYdFoEQFIZBRLLoFBHYkAI4YFBKYYFHCIodFLO8M4RHDh4FCKYMHwQFELIkPBYQdFFIVCLK8AAAg=")) diff --git a/apps/oxofocus/app.js b/apps/oxofocus/app.js new file mode 100644 index 000000000..5b02116e6 --- /dev/null +++ b/apps/oxofocus/app.js @@ -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(); diff --git a/apps/oxofocus/app.png b/apps/oxofocus/app.png new file mode 100644 index 0000000000000000000000000000000000000000..1e47fdc7b6bebc619a43c6cf92c29f6483a3ef4c GIT binary patch literal 594 zcmV-Y0D|nPY9eVAHPC-(7(XJ;#m)PfQ#|q zQ7`@&l!!OPKOzUr>NJ@&?e4bFcG~nM8|Wl&nYTO3z6=2W=mHp)Y;-AibO7v1Iyy># zOAq4|t1vE@>v92q4+$ZH>t>iln?@S$UINYmTt^Un0AAcg3~HNKtX4k&40K5xTEK*2 zQx=<1%#WLhCBFsmMe|JP{Wc^bQvnWu2a4aa~)!LGJqxS1T{ITPUTDc~k=0W-GpFYG&8Cm<-H4V6uRCdG0qA)jJOgu)Sk zSBhO4k{Hl|-O#G5@lCO_yaad=C36hmDYOf5N%R4{v+`R2=;@M>dXcq?y2xxHg6mer z%r!>-V3l%T|@X79h6_uQcdlAzC)ON)WEnq~q z?wUT>XLh=0;7%xZpQI~(XaTDTJKZ1=Zw9Wzy5e`NHyd;aOJS#-q+M}|c6y$qE1svF zUKqGMdG-3tS}%p$=^y^%wBZoXBD&&Nc?pQK(_IU$m!>P8wViII7k_FXvfe2 g7XN1tl=<&AzbC_?E5c-wzW@LL07*qoM6N<$f?%cvjsO4v literal 0 HcmV?d00001 diff --git a/apps/oxofocus/metadata.json b/apps/oxofocus/metadata.json new file mode 100644 index 000000000..60c84fab5 --- /dev/null +++ b/apps/oxofocus/metadata.json @@ -0,0 +1,18 @@ +{ "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!", + "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"] +} diff --git a/apps/oxofocus/screenshot.png b/apps/oxofocus/screenshot.png new file mode 100644 index 0000000000000000000000000000000000000000..4f05fe2e9a4393aa8a576cae1cd61e06aa35b99a GIT binary patch literal 3282 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 U-IxqV{+|gj(l^yB(V;~C2Quq$-v9sr literal 0 HcmV?d00001 diff --git a/apps/oxofocus/screenshot2.png b/apps/oxofocus/screenshot2.png new file mode 100644 index 0000000000000000000000000000000000000000..c1ef445f7fe1ae484d02572741a16cf11586e898 GIT binary patch literal 3306 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 Date: Thu, 28 Sep 2023 18:35:31 +0100 Subject: [PATCH 2/5] Oxofocus - added ChangeLog --- apps/oxofocus/ChangeLog | 1 + 1 file changed, 1 insertion(+) create mode 100644 apps/oxofocus/ChangeLog diff --git a/apps/oxofocus/ChangeLog b/apps/oxofocus/ChangeLog new file mode 100644 index 000000000..6728683c8 --- /dev/null +++ b/apps/oxofocus/ChangeLog @@ -0,0 +1 @@ +0.01: first version From ab4a5b8ccebc8d8a2a16dfeb435c7f1f00cb837a Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Thu, 28 Sep 2023 18:50:39 +0100 Subject: [PATCH 3/5] Oxofocus - added README to metadata --- apps/oxofocus/metadata.json | 1 + 1 file changed, 1 insertion(+) diff --git a/apps/oxofocus/metadata.json b/apps/oxofocus/metadata.json index 60c84fab5..6457fe76c 100644 --- a/apps/oxofocus/metadata.json +++ b/apps/oxofocus/metadata.json @@ -4,6 +4,7 @@ "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"}, From 28cbfa37f602f2c9cd962f45cf6e11188f5aa37b Mon Sep 17 00:00:00 2001 From: Hugh Barney Date: Thu, 28 Sep 2023 19:10:23 +0100 Subject: [PATCH 4/5] Oxofocus - README update --- apps/oxofocus/README.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/apps/oxofocus/README.md b/apps/oxofocus/README.md index f65d0f9ca..736d04e0c 100644 --- a/apps/oxofocus/README.md +++ b/apps/oxofocus/README.md @@ -2,6 +2,10 @@ A Naughts and Crosses game that learns as it goes +![](screenshot.png) +![](screenshot1.png) +![](screenshot2.png) + 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 From 1a18a5463ba2380cfcbba789c300a4b2f6afb22f Mon Sep 17 00:00:00 2001 From: Gordon Williams Date: Fri, 29 Sep 2023 09:32:49 +0100 Subject: [PATCH 5/5] Update apps/oxofocus/app.js Co-authored-by: Rob Pilling --- apps/oxofocus/app.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apps/oxofocus/app.js b/apps/oxofocus/app.js index 5b02116e6..87c8beee0 100644 --- a/apps/oxofocus/app.js +++ b/apps/oxofocus/app.js @@ -454,7 +454,7 @@ Bangle.on('touch', function(zone,e) { if (game_state == GAME_OVER) { new_game(); - return(); + return; } get_move();