看过《赌神》系列电影的小伙伴知道,主角在赌桌上随机掷骰子,想要几点就要几点,这种技能可谓是神乎其技,令我们大饱眼福的同时也甚是羡慕,好想在现实生活中直接展示一番。
笔者虽然做不到这样的"神技",但是在之前开发中遇到了这样的需求,当下冥思苦想总算找到了掷点数的"奥秘"。
掷一个骰子的情况:
一个骰子结果点数只能在[1,6]的范围中选择
。我投掷一个骰子,骰子结果比1小不行,比6大同样不行。注意:这里是不允许破坏骰子的。
掷两个骰子的情况:两个骰子结果点数是在[2,12]之间选择
。我现在想掷7点,两个骰子点数"互补" ,假设我第一次掷了6点,剩下的骰子点数就是1点;假设我第一次掷了5点,剩下的骰子点数只能是2点;假设我第一次掷了4点,剩下的骰子点数只能是3点...假设我第一次掷了1点,剩下的骰子数就得是6点了。
掷三个骰子的情况:三个骰子结果点数是在[3,18]之间选择
。我现在想掷10点,那么当第一个骰子为1点时,剩下两骰子就得分9点,如果当前第一个骰子是6点时,剩下两骰子就得分4点。每个骰子必须保证有值,最少是1点,最多6点。
总结以上规律,就会发现:
-
掷骰子必须保证有值。不管掷几点,骰子肯定有值,是1~6的随机值。
-
骰子点数是有范围的。一个骰子至少需要1点,最多也只能达到6点,一旦超出6点(小于1点)都认定投掷失败。
-
掷骰子次数不变,但剩余骰子要想得到预期结果的机会实际是逐步减少的。随着投掷骰子的次数增多,后序的骰子需要尽可能采取方式来"凑齐"最终的结果,否则本轮掷骰子是不成功的。
-
骰子之间展示的结果是相互影响的。诚然投掷骰子可以"贪一点",前期直接从最大点掷,那么剩下的点数可能需要往小点数掷,但有时候可能会出现0点/负数的存在,就需要重新投掷;如果前期掷骰子掷了最小点,那么剩下的投掷就可能需要往大点数掷,但这时候又意味着出现超过6点的情况。
投掷骰子的步骤
第一步: 实现投掷点数的随机值,每次投掷骰子的结果是有一个范围的,依据范围获取随机值。
js
/**
* 获取最大最小值之间的随机值
* @param {Number} -min 最小值
* @param {Number} -max 最大值
*/
function getRandom(min,max) {
return Math.floor(Math.random(min,max) * (max -min + 1) + min);
}
第二步: 既然已经获取到了每次的随机值,那么就可先确定好函数的大部分逻辑.
js
/**
* 根据点数和获取骰子点数的方法
* @param {Number} -sum 投掷骰子的总和
* @param {Number} -num 投掷骰子的个数 默认为3个
*/
function getPoints(sum, num = 3) {
// 骰子总和不能小于骰子数 或者 骰子总和不能超过骰子数*6点
if(sum < num || sum > num * 6) {
throw new Error('投掷结果有误');
return 0;
}
// 当骰子投掷结果刚好都是1点 无须计算
if(sum === num * 1) {
return new Array(num).fill(1);
}
// 当骰子投掷结果刚好都是6点 无须计算
if(sum === num * 6) {
return new Array(num).fill(6);
}
// 变量result用于收集可能投掷的骰子结果
let result = [];
// 进行掷骰子的操作。。。
return result;
}
第三步: 确认掷骰子的范围 ---核心步骤
根据上述现象,当骰子只有一个,范围是[1,6],骰子投掷结果有着自身的限制。
当骰子有两个时,为了保证每个骰子都有结果,第一个骰子投掷时是不是就得保证第二个骰子至少有一点(保证了另一个骰子的下限)。而当第一个骰子投掷如果是1点,一旦点数和(sum)超过7点,第二个骰子的点数必定会超出6点(同时需要保证另一个骰子的上限)。两个骰子投掷的结果不仅受投掷的点数和(sum)的影响,两个骰子的点数还相互之间有影响。
当骰子有三个时,也符合此现象,影响的范围更广。
所以如果在投掷骰子之前就判定好当前还有几次投掷的机会(剩下的投掷骰子数),并做出本轮投掷的点数限制,就可以尽可能保证本次投掷不会影响到其他投掷骰子。
js
/**
* 根据点数和获取每次骰子结果的最大最小值
* @param {Number} -sum 投掷骰子的总和
* @param {Number} -lastNum 剩下投掷的骰子数
*/
function getMaxAndMin(sum, lastNum) {
const max = sum - lastNum * 1 > 6 ? 6: sum - lastNum * 1;
const min = sum - lastNum * 6 < 1 ? 1: sum - lastNum * 6;
return {
max,
min
}
}
第四步: 完善掷骰子操作
js
/**
* 根据点数和获取骰子点数的方法
* @param {Number} -sum 投掷骰子的总和
* @param {Number} -num 投掷骰子的个数 默认为3个
*/
function getPoints(sum, num = 3) {
// 骰子总和不能小于骰子数 或者 骰子总和不能超过骰子数*6点
if(sum < num || sum > num * 6) {
throw new Error('投掷结果有误');
return 0;
}
// 当骰子投掷结果刚好都是1点
if(sum === num * 1) {
return new Array(num).fill(1);
}
// 当骰子投掷结果刚好都是6点
if(sum === num * 6) {
return new Array(num).fill(6);
}
// 变量result用于收集可能投掷的骰子结果
let result = [];
let tmp = sum;
while(num > 1) {
num--;
// 获取最大最小值
const {min, max} = getMaxAndMin(tmp, num);
// 随机出本轮的值
const value = getRandom(min,max);
// 放入结果
result.push(value);
// 点数和减少进入下次判断
tmp -= value;
}
// 将剩余值放入
result.push(tmp);
return result;
}
完整代码展示
js
/**
* 获取最大最小值之间的随机值
* @param {Number} -min 最小值
* @param {Number} -max 最大值
*/
function getRandom(min,max) {
return Math.floor(Math.random(min,max) * (max -min + 1) + min);
}
/**
* 根据点数和获取每次骰子结果的最大最小值
* @param {Number} -sum 投掷骰子的总和
* @param {Number} -lastNum 剩下投掷的骰子数
*/
function getMaxAndMin(sum, lastNum) {
const max = sum - lastNum * 1 > 6 ? 6: sum - lastNum * 1;
const min = sum - lastNum * 6 < 1 ? 1: sum - lastNum * 6;
return {
max,
min
}
}
/**
* 根据点数和获取骰子点数的方法
* @param {Number} -sum 投掷骰子的总和
* @param {Number} -num 投掷骰子的个数 默认为3个
*/
function getPoints(sum, num = 3) {
// 骰子总和不能小于骰子数 或者 骰子总和不能超过骰子数*6点
if(sum < num || sum > num * 6) {
throw new Error('投掷结果有误');
return 0;
}
// 当骰子投掷结果刚好都是1点
if(sum === num * 1) {
return new Array(num).fill(1);
}
// 当骰子投掷结果刚好都是6点
if(sum === num * 6) {
return new Array(num).fill(6);
}
// 变量result用于收集可能投掷的骰子结果
let result = [];
let tmp = sum;
while(num > 1) {
num--;
// 获取最大最小值
const {min, max} = getMaxAndMin(tmp, num);
// 随机出本轮的值
const value = getRandom(min,max);
// 放入结果
result.push(value);
// 点数和减少进入下次判断
tmp -= value;
}
// 将剩余值放入
result.push(tmp);
return result;
}
console.log(getPoints(10));
console.log(getPoints(22,5));
console.log(getPoints(1,2)); // 报错 Uncaught Error: 投掷结果有误
题外话
大家要是觉得有用,辛苦点赞!!送❀❀!