1A2B 猜數字遊戲
19 Sep 2019會試著寫這個小遊戲的原因是起因於強者我朋友說想寫個小東西當作練習 Javascript,剛好我也是 JS 的新手需要練習一下所以就把他的發想撿來自己寫寫看了。
先附上最後寫完的成果:janelin612/1a2b
遊戲本體
出題
出題的部分本質上就是怎麼從 0~9 之中隨機挑出四個不重複的數字。
我最後選擇直接準備好一個 0~9 的陣列,然後隨機選一個之後把選中的從陣列中移除,這樣選四次。
let pool = ["0", "1", "2", "3", "4", "5", "6", "7", "8", "9"];
for (let i = 0; i < 4; i++) {
let index = Math.floor(Math.random() * pool.length);
answer.push(pool[index]);
pool.splice(index, 1);
}
總覺得有更好的做法
判斷
我原本以為這遊戲最值得討論的部分是如何寫判斷幾 A 幾 B,但實際寫一遍發現很簡單,把猜的數字跟答案都拆成陣列之後依序檢查數字有沒有存在,存在的話再追加確認位置對不對就結束了。
let a = 0;
let b = 0;
guess.split("").forEach((val, id) => {
let index = answer.indexOf(val);
if (index != -1) {
if (index == id) {
a++;
} else {
b++;
}
}
}
解題機器人
遊戲最後花了一個下午的時間把 UI 跟邏輯兜出來,寫完總覺得有點…太空虛了。
於是異想天開決定補上一個自動解題的機器人,設計的時候有加上一個目標是要用最少的次數猜出答案,不然用迴圈把全部的數字猜一遍就好,沒啥挑戰性。
正解
程式碼蠻長的,我就不整段貼上來了,直接附上連結:
1a2b/robot.js
大致上的做法是:
- 生成所有不重複數字組合,共 10x9x8x7=5040 種
- 根據回傳的 AB 結果過濾掉與之衝突的其他候選答案
- 重複直到候選答案剩 1 個或者剛好 4A 命中
例如你猜 1234 拿到 2A0B 的話,就要把所有 3A 的結果濾掉(1x34 12x4 123x),又例如你猜 5678 拿到 1A0B 的話,就要把 2A 以上的結果濾掉(5xx8 56xx xx78 …etc)
let _c = this._checker;
if (a == 3) {
//當3A時,則留下與猜的答案有3個一樣的且不能有4個一樣的 後面依此類推
this._pool = this._pool.filter((val) => {
return _c.fix3(val, ans) && !_c.fix4(val, ans);
});
}
透過這樣的作法,幾乎可以保證在 7 次內猜到正確答案。
棄案
討論正解很無趣,因為一定早就有人寫出來了,搞不好人家的演算法還比你的快。所以我想紀錄一下中間找解法的時候想到的一些腦動大開的作法。
1.模擬人的行為
第一個想到的做法肯定就是想要複製我的決策過程,例如在猜到 3A0B 的時候會試圖去替換掉其中一個數字,然後再根據新猜出來的結果去回推我替換掉的數字是正解還是那個錯誤的。
這樣的問題在於,程式碼要能夠知道自己在幹嘛並決定後續的決策,再根據結果判斷決策是否正確。那我不就得對所有的行為進行定義,例如”替換一個數字”、”調動數字的順序”之類的。
我覺得更像是在寫一個 AI,所以直接放棄,太難了 XD
2.權重
模擬人的行為太困難,那要怎麼像個電腦一樣的思考呢?我想到的是替每個位置的每個數字準備一個權重分數,變成一張 X 軸是第一格到第四格,Y 軸是 0~9 的矩陣,矩陣內就填入權重分數,然後比對前後猜測的結果去計算比重。
例如 A 從 3A 掉成 2A,就表示猜的結果離正解越來越遠了,那剛剛調動的數字權重就應該調低,也就是”那個位置傾向不屬於他”的概念。
後來放棄的原因是這樣沒有猜比較少次,因為這樣的設計反而需要猜很多次,權重的差異化才會出來,答案才會有意義。