-
Notifications
You must be signed in to change notification settings - Fork 2.3k
/
game.js
147 lines (140 loc) · 5.17 KB
/
game.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
/**
* @license
* Copyright 2019 Google LLC. All Rights Reserved.
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* =============================================================================
*/
/**
* This file implements a two player card game similar to a much simplified game
* of Poker. The cards range in value from 1 to GAME_STATE.max_card_value, and
* each player receives three cards, drawn uniformly from the integers in the
* range [1, GAME_STATE.max_card_value]. One player wins by having a better
* hand.
*
* Any triple (three of the same card) beats any double.
* Any double (two of the same card) beats any single.
* If both players have a triple, the higher triple wins.
* If both players have a double, the higher double wins.
* If neither player has a double, the player with the highest
* individual number wins.
* Ties are settled randomly, 50/50.
*/
// Global exported state tracking the rules of the game and how many games have
// been played so far. Individual fields may be read / changed by external
// control.
export const GAME_STATE = {
// The number of times the game has been played. Useful
// for illustrating how many simulations it takes to train the model.
num_simulations_so_far: 0,
// Constants defining the range of card values.
min_card_value: 1,
max_card_value: 13,
// Controls how many cards per hand. Controllable from a UI element.
num_cards_per_hand: 3
};
/**
* Returns a random integer in the range [GAME_STATE.min_card_value,
* GAME_STATE.max_card_value]
*/
export function getRandomDigit() {
return Math.floor(
GAME_STATE.min_card_value + Math.random() * GAME_STATE.max_card_value);
}
/**
* Yields a randomly generated array of three elements. Each element
* is an integer randomly selected within the range [1, 9]. Hands are returned
* in sorted order.
*/
export function randomHand() {
const hand = [];
for (let i = 0; i < GAME_STATE.num_cards_per_hand; i++) {
hand.push(getRandomDigit());
}
return hand.sort((a, b) => a - b);
}
/**
* This function produces an array indicating the largest face value for
* every possible group size (1 card, 1 pair, 3-of-a-kind, 4-of-a-kind, etc.),
* in the hand. Zeros indicate there are no groups of that size.
*
* E.g., if this function returns [9, 3, 0, 0, 0, 0] it indicates that
* - The highest card is a 9
* - The highest double is of a 3
* - There are no triples etc.
*
* This could result if the hand were, e.g, [1, 2, 2, 3, 3, 9]
*
* @param {number[]} hand A sorted integer array of length
* GAME_STATE.num_cards_per_hand
* @returns {number[]} An array of the face value of the largest value for each
* group size. Zero indicates there are no examples of that group size in
* the hand, if, for instance, there are no triples.
*/
export function handScoringHelper(hand) {
// Create an array of all zeros of the appropriate size.
const faceValOfEachGroup = [];
for (let i = 0; i < GAME_STATE.num_cards_per_hand; i++) {
faceValOfEachGroup.push(0);
}
let runLength = 0;
let prevVal = 0;
for (let i = 0; i < GAME_STATE.num_cards_per_hand; i++) {
const card = hand[i];
if (card == prevVal) {
runLength += 1;
} else {
prevVal = card;
runLength = 1;
}
faceValOfEachGroup[runLength - 1] = card;
}
return faceValOfEachGroup;
}
/**
* Returns 1 if hand1 beats hand2, in terms of hand value.
* Returns 0 if hand1 is less than hand2.
* In the event of a tie, return 0 or 1 randomly with even odds.
* @param {number[]} hand1 ordered list of numbers representing Player 1's hand.
* @param {number[]} hand2 ordered list of numbers representing Player 2's hand.
* @returns {number} 1 or 0 indicating Player 1's win or loss, respectively.
*/
export function compareHands(hand1, hand2) {
const handScore1 = handScoringHelper(hand1);
const handScore2 = handScoringHelper(hand2);
// In descending order of group size, decide if one hand is better.
for (let group = GAME_STATE.num_cards_per_hand - 1; group >= 0; group--) {
if (handScore1[group] > handScore2[group]) {
return 1;
}
if (handScore1[group] < handScore2[group]) {
return 0;
}
}
// Break a tie by flipping a fair coin.
if (Math.random() > 0.5) {
return 1;
}
return 0;
}
/**
* Returns an object representing one complete play of the game.
* Generates two random hands, and the value of the hand comparison.
* Returns [hand1, hand2, whetherHand1Wins.
*/
export function generateOnePlay() {
const player1Hand = randomHand();
const player2Hand = randomHand();
const player1Win = compareHands(player1Hand, player2Hand);
GAME_STATE.num_simulations_so_far++;
return {player1Hand, player2Hand, player1Win};
}