一位賭狗前端的自我修養

YDJFE發表於2018-11-21

這段時間工作巨多,直到今天才有時間寫點東西。可是要輸出點什麼比較好呢?這時候看到了隔壁桌面上放著一張機打的寫著一組一組數字的紙,有了!那要不抽個獎吧!

問題分析

抽獎,講究一個隨機,只要把隨機搖號解決了不就不管什麼規則都能抽了?所以我們需要一個可以產生隨機數的函式。不僅要隨機數,我們還需要的是一個閉區間隨機取整數的函式。所以

const random = (m, n) => m + Math.floor(Math.random() * (n - m))
複製程式碼

沒吃過豬肉還見過豬跑,號碼肯定是抽一個少一個,所以根本不存在隨機數能相同的情況!所以這個方法是不能用的。那麼,拿來抽的方法肯定得滿足三個條件:

  • 有範圍,是閉區間
  • 起始值肯定大於1
  • 隨機數不能有重複

問題解決

關於隨機不重複我最先想到的是利用sort對陣列隨機打散的方法。這個時候可能會有其他聲音:

要不要這麼水?sort用來排序的不懂?

水不水不知道,看一下MDN。

arr.sort([compareFunction])

  • 如果 compareFunction(a, b) 小於 0 ,那麼 a 會被排列到 b 之前;
  • 如果 compareFunction(a, b) 等於 0 , a 和 b 的相對位置不變。備註: ECMAScript 標準並不保證這一行為,而且也不是所有瀏覽器都會遵守(例如 Mozilla 在 2003 年之前的版本);
  • 如果 compareFunction(a, b) 大於 0 , b 會被排列到 a 之前。
  • compareFunction(a, b) 必須總是對相同的輸入返回相同的比較結果,否則排序的結果將是不確定的。

so

// arr 陣列,num 取個數
const random = (arr:number[], num: number) =>
  arr.sort(() => Math.random() - 0.5).splice(0, num)

複製程式碼

具體問題具體解決

首先,我們需要通過百度查一下業界知名抽獎活動典型的遊戲規則:

某抽獎遊戲分為紅色區和藍色區,紅色區由1-33共三十三個號碼組成,藍色區由1-16共十六個號碼組成。需要在紅色區選擇6個號碼和在藍色區選擇1個號碼合成一次有效號碼組。

作為賭狗前端,提取這段話幾個關鍵點:

  • 紅色區 1 ~ 33
  • 藍色區 1 ~ 16
  • 取紅 6 個,取藍 1 個

別說了我腦子裡又有函式了

// ...random

const dualColor = () => {
  const reds = [1, 2, ..33]
  const blues = [1, 2, ..16]

  const red = random(reds, 6)
  const blue = random(blues, 1)

  return [red, blue]
}
複製程式碼

這個時候我們看一眼方法,你這方法不對啊!以紅色為例子,我們的函式表達結果是:直接把號碼打散後流出前 6 個,而遊戲規則指出號碼不僅一個一個放出,而且放的過程中並沒有停止打散。所以這個隨機函式顯然不合理。那還能怎麼辦,改啊!

一位賭狗前端的自我修養

// 打散後流出第一個和剩下的號碼組
function random(arr) {
  const newarr = arr.sort(() => Math.random() - 0.5)
  const val = newarr.shift()
  return [val, newarr]
}

function dualColor() {
  let redballs = [1, 2, ..33]
  let blueballs = [1, 2, ..16]
  let red = [], blue = []

  for (let i = 0; i < 6; i++) {
    const balls = random(redballs)
    red.push(balls[0])
    redballs = balls[1]
  }

  blue.push(random(blueballs)[0])

  return [red, blue]
}
複製程式碼

做法優化

說實話這麼寫程式可憋死我了…

不會真的有人這麼寫陣列吧

如果按照正常隨機數方法那麼做的話,我確實提供一個最大值一個最小值就結束了,關鍵這是個陣列,又沒有什麼range之類的可以用,不會真的有人手寫 1 ~ 33 吧。不過我們可以曲線撈一下

// 0 ~ 9
const arr = [...Array(10).keys()]
複製程式碼

所以第一點改造兩個球陣列

  let reds: number[] = [...Array(33).keys()].map(i => i+1)
  let blues: number[] = [...Array(16).keys()].map(i => i+1)
複製程式碼

為什麼 +1 呢?號碼也沒從 0 開始的啊!

也許我們應該讓隨機函式更純更通用?

我們需要讓抽獎更靈活一些,畢竟只要是抽的,我全都要。我想讓隨機函式能接受一個陣列和我想要的個數,再返回結果。

同樣的會出現需要迴圈取值的情況,為什麼我們不用神奇的尾遞迴呢?

function randomVal(
  arr: number[],
  total: number,
  temp: number[] = [],
): number[] {
  const [head, ...body] = arr
    .sort(() => Math.random() - 0.5)
  return !total
    ? temp
    : randomVal(body, total - 1, temp.concat(head));
}
複製程式碼

俺尋思這兩個號組寫起來可太累了

改!都可以改!我們可以改成存放起始值和終止值的元組

function dualColor() {
  const reds: [number, number] = [1, 33]
  const blues: [number, number] =  [1, 16]

  return [randomVal(reds, 6), randomVal(blues, 1)]
}
複製程式碼

相應的隨機函式也做點小改動

function randomVal(
  fromto: number[],
  total: number,
  temp: number[] = [],
): number[] {
  const [head, ...body] = (temp.length
    ? fromto
    : [...Array(fromto[1]).keys()]
        .map(item => item + 1)
        .splice(fromto[0] - 1)
  ).sort(() => Math.random() - 0.5);
  return !total
    ? temp
    : randomVal(body, total - 1, temp.concat(head))
}
複製程式碼

我好像看漏了規則

除此之外還有三種: (一)從紅色區中選擇7--20個,從藍色區中選擇1個。 (二)從紅色區中選擇6個,從藍色區中選擇2--16個。 (三)從紅色區中選擇7--20個,從藍色區中選擇2--16個。

一位賭狗前端的自我修養

雖然我看不懂,但是這麼改就沒問題了吧

// 我管你幾個,全給你安排上!
function dualColor(red: number = 6, blue: number = 1) {
  const reds: [number, number] = [1, 33]
  const blues: [number, number] =  [1, 16]

  return [randomVal(reds, red), randomVal(blues, blue)]
}
複製程式碼

所以最後解決方案是

function dualColor(red: number = 6, blue: number = 1) {
  const reds: [number, number] = [1, 33]
  const blues: [number, number] = [1, 16]

  return [randomBall(reds, red), randomBall(blues, blue)]
}

function randomBall(
  fromto: number[],
  total: number,
  temp: number[] = [],
): number[] {
  const [head, ...body] = (temp.length
    ? fromto
    : [...Array(fromto[1]).keys()]
        .splice(fromto[0] - 1)
        .map(item => item + 1)
  ).sort(() => Math.random() - 0.5);
  return !total
    ? temp
    : randomBall(body, total - 1, temp.concat(head))
}
複製程式碼

賞心悅目?

相關文章