Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#118 layouts with bitmask #121

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@
"test": "yarn workspace @regen-bingo/backend test",
"clean": "yarn workspace @regen-bingo/backend clean",
"deploy": "yarn workspace @regen-bingo/backend deploy",
"random": "yarn workspace @regen-bingo/backend hardhat run scripts/provide_randomness.ts"
"random": "yarn workspace @regen-bingo/backend hardhat run scripts/provide_randomness.ts",
"layout": "yarn workspace @regen-bingo/backend hardhat run scripts/generate_layouts.ts"
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this validate_layouts.

Read LAYOUTS_COUNT and layouts with hardhat-exposed. Compare the layouts with the hardhat script generated one. Lastly, print the layouts and example cards as I commented there.

Copy link
Collaborator Author

@koybasimuhittin koybasimuhittin Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Make this validate_layouts.

Read LAYOUTS_COUNT and layouts with hardhat-exposed. Compare the layouts with the hardhat script generated one. Lastly, print the layouts and example cards as I commented there.

Tests for the layouts done in here

},
"workspaces": {
"packages": [
Expand Down
68 changes: 28 additions & 40 deletions packages/backend/contracts/RegenBingo.sol
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ contract RegenBingo is ERC721A, VRFV2WrapperConsumerBase {
//////////////////////////////////////////////////////////////*/

uint8 constant NUMBERS_COUNT = 90;
uint256 constant LAYOUTS_COUNT = 3;
uint256 constant LAYOUTS_COUNT = 10;
uint256 constant ROWS_COUNT = 3;
uint256 constant COLUMNS_COUNT = 9;
uint32 constant VRF_CALLBACK_GAS_LIMIT = 100000;
Expand All @@ -50,22 +50,18 @@ contract RegenBingo is ERC721A, VRFV2WrapperConsumerBase {
// lowest_available_number + (seed % possible_options)
// Following these rules: https://en.wikipedia.org/wiki/Bingo_card#90-ball_bingo_cards
// Using these as templates: https://www.scribd.com/document/325121782/1-90-British-Bingo-Cards
uint8[2][COLUMNS_COUNT][ROWS_COUNT][LAYOUTS_COUNT] layouts = [
[
[[ 1, 9], [ 0, 0], [ 0, 0], [ 0, 0], [40, 5], [56, 4], [60,10], [77, 3], [ 0, 0]],
[[ 0, 0], [ 0, 0], [ 0, 0], [30,10], [45, 5], [53, 3], [ 0, 0], [74, 3], [80, 6]],
[[ 0, 0], [10,10], [20,10], [ 0, 0], [ 0, 0], [50, 3], [ 0, 0], [70, 4], [86, 5]]
],
[
[[ 6, 4], [10, 5], [ 0, 0], [ 0, 0], [ 0, 0], [ 0, 0], [66, 4], [75, 5], [80,11]],
[[ 0, 0], [ 0, 0], [25, 5], [30,10], [40,10], [50,10], [63, 3], [ 0, 0], [ 0, 0]],
[[ 1, 5], [15, 5], [20, 5], [ 0, 0], [ 0, 0], [ 0, 0], [60, 3], [70, 5], [ 0, 0]]
],
[
[[ 1, 5], [ 0, 0], [25, 5], [ 0, 0], [ 0, 0], [50,10], [ 0, 0], [70, 5], [88, 3]],
[[ 0, 0], [10,10], [20, 5], [30,10], [ 0, 0], [ 0, 0], [65, 5], [ 0, 0], [84, 4]],
[[ 6, 4], [ 0, 0], [ 0, 0], [ 0, 0], [40,10], [ 0, 0], [60, 5], [75, 5], [80, 4]]
]

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There should not be any irrelevant comments in the code at any point. You better update them, but at the very least delete irrelevant comments. The comments above is irrelevant at this time.

Since this is not a readable and understandable code, you must provide comments, at the very least add TODO comments.

Please read the following source in full: https://stackoverflow.blog/2021/12/23/best-practices-for-writing-code-comments/
Summary of their rules:

Rule 1: Comments should not duplicate the code.

Rule 2: Good comments do not excuse unclear code.

Rule 3: If you can’t write a clear comment, there may be a problem with the code.

Rule 4: Comments should dispel confusion, not cause it.

Rule 5: Explain unidiomatic code in comments.

Rule 6: Provide links to the original source of copied code.

Rule 7: Include links to external references where they will be most helpful.

Rule 8: Add comments when fixing bugs.

Rule 9: Use comments to mark incomplete implementations.

uint256[LAYOUTS_COUNT] public layouts = [
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There's no need for this to be public. Every public function adds bytecode deployment cost and pollutes the ABI.

74531863127069656875678005463629317377624456585407760715211671503378212480908,
84733770693983134393286414926143418191632064005497261285059935046721726341764,
88892850850319190340242688960817664513518456655726863909825725454469825060928,
85615516063282425514350087369392836674708439228389954229085207788303887704128,
82025225875150254732266191914090518340421847287971611702527960659275601731648,
81428084179800062446453646573230984879745244988152209000518516361294873969284,
70657384472911031446975028864715540995046624405935865290481737135510018089024,
96073477329873783590368928870704280664394527774970734350082854071566790058628,
71254418773555781409572465841860736354607880829542776466442355105762863825540,
81712411770574619635979783041230964642099925492660987369159605175257838198848
];

/*//////////////////////////////////////////////////////////////
Expand Down Expand Up @@ -289,14 +285,18 @@ contract RegenBingo is ERC721A, VRFV2WrapperConsumerBase {
{
require(ownerOf(tokenId) != address(0), "Invalid card");
uint256 tokenSeed = _tokenSeed(tokenId);
for (uint256 row = 0; row < ROWS_COUNT; row++) {
for (uint256 column = 0; column < COLUMNS_COUNT; column++) {
numberMatrix[row][column] = getNumberByCoordinates(
tokenSeed,
row,
column
);
}
uint256 layout = layouts[tokenSeed % LAYOUTS_COUNT];

for(uint256 i = 0; i < 15; i++) {
uint256 row = layout % 4; // 2 bit
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"2 bits" etc. as they're plural. Make these comments more explanatory.

layout /= 4;
uint256 column = layout % 16; // 4 bit
layout /= 16;
uint256 floorNumber = layout % 128; // 7 bit
layout /= 128;
uint256 range = layout % 16; // 4 bit
layout /= 16;
numberMatrix[row][column] = uint8(floorNumber + tokenSeed % range);
}
}

Expand All @@ -306,12 +306,13 @@ contract RegenBingo is ERC721A, VRFV2WrapperConsumerBase {
returns (bool[COLUMNS_COUNT][ROWS_COUNT] memory isDrawnMatrix)
{
require(ownerOf(tokenId) != address(0), "Invalid card");
uint256 tokenSeed = _tokenSeed(tokenId);
uint8[COLUMNS_COUNT][ROWS_COUNT] memory matrix = numberMatrix(tokenId);

for (uint256 row = 0; row < ROWS_COUNT; row++) {
for (uint256 column = 0; column < COLUMNS_COUNT; column++) {
if (
isDrawn(
getNumberByCoordinates(tokenSeed, row, column)
matrix[row][column]
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As this's shorter, inline the condition, please.

)
) {
isDrawnMatrix[row][column] = true;
Expand All @@ -336,19 +337,6 @@ contract RegenBingo is ERC721A, VRFV2WrapperConsumerBase {
}
}

function getNumberByCoordinates(
uint256 tokenSeed,
uint256 row,
uint256 column
) public view returns (uint8) {
uint8[2][COLUMNS_COUNT][ROWS_COUNT] memory layout = layouts[tokenSeed % LAYOUTS_COUNT];
if (layout[row][column][0] == 0) {
return 0;
} else {
return layout[row][column][0] + uint8(tokenSeed % layout[row][column][1]);
}
}

function getDrawnNumbers() public view returns (uint8[] memory) {
uint8[] memory drawnNumbers = new uint8[](drawnNumbersCount);
for (uint8 i = 0; i < drawnNumbersCount; i++) {
Expand Down
257 changes: 257 additions & 0 deletions packages/backend/scripts/generate_layouts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,257 @@
import { BigNumber } from "ethers"
import { keccak256, toUtf8Bytes } from "ethers/lib/utils"

type Layout = number[][][] // 9 * 3 * 2
let nonce = 0

const generateNextRandom = (): number => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please give credit to the sources of codes, algorithms, and ideas you use. Not only here, but in general.

nonce += 1
let a = nonce * 15485863
return (a * a * a % 2038074743) / 2038074743
}

const isValidLayout = (layout : Layout) : Boolean => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. In case of invalid layouts, return false ASAP. In the end return true.
  2. There's no rule regarding all rows having 5 numbers. Please remove it. Speaking of that, please review the docs on British Bingo cards. There could be rules I don't know (very very unlikely).
  3. This function is not checking the required conditions on the ranges of the numbers.
  4. This function is not checking if these number ranges are overlapping.

Please rewrite it based on the following:

const sumOfOptionsInColumn = (layout: Layout, columnIndex: number) =>
    layout[columnIndex][0][0] + layout[columnIndex][1][0] + layout[columnIndex][j][0];

// Validate that the first column has 9 options
if (sumOfOptionsInColumn(0) != 9) {
    return false;
}

// Validate that the middle columns have 10 options
for(let middleColumnIndex = 1; middleColumnIndex < 8; middleColumnIndex++) {
    if (sumOfOptionsInColumn(middleColumnIndex) != 10) {
        return false;
    }
}

// Validate that the last column has 11 options
if (sumOfOptionsInColumn(8) != 11) {
    return false;
}

// Validate that the first column has all the options for [1,9]
// TODO

// Validate that the middle columns have all the options for [10,19], ..., [70,79]
// TODO

// Validate that the first column has all the options for [80,90]
// TODO

return true;

Copy link
Collaborator Author

@koybasimuhittin koybasimuhittin Feb 3, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2. There's no rule regarding all rows having 5 numbers. Please remove it. Speaking of that, please review the docs on British Bingo cards. There could be rules I don't know (very very unlikely).

Followed this rules that are in the Regen Bingo Contract comments and checked for if any column has at least one number.


let isValidRow = true
let isValidColumn = true

for(var i = 0; i < 9; i++) {
let columnCount = 0
for(var j = 0; j < 3; j++) {
if(layout && layout[i] && layout[i][j] && layout[i][j][0] && layout[i][j][0] != 0) {
columnCount += 1
}
}
if(columnCount == 0) {
isValidColumn = false
}
}

for(var i = 0; i < 3; i++) {
let rowCount = 0
for(var j = 0; j < 9; j++) {
if(layout && layout[j] && layout[j][i] && layout[j][i][0] && layout[j][i][0] != 0) {
rowCount += 1
}
}
if(rowCount != 5) {
isValidRow = false
}
}

return isValidColumn && isValidRow
}


const writeNumber = (number: Number): String => {
if(number < 10) {
return ' ' + String(number)
}
else {
return String(number)
}
}


function layoutWriter(layout: Layout){
console.log('[')
for(var i = 0; i < 3; i++){
process.stdout.write(' [')
for(var j = 0; j < 9; j++){
if(j < 8) {
process.stdout.write('[' + writeNumber(layout[j][i][0]) + ',' + writeNumber(layout[j][i][1]) + '], ')
}
else {
if(i < 2) {
process.stdout.write('[' + writeNumber(layout[j][i][0]) + ',' + writeNumber(layout[j][i][1]) + ']],\n')
}
else {
process.stdout.write('[' + writeNumber(layout[j][i][0]) + ',' + writeNumber(layout[j][i][1]) + ']]\n')
}
}
}
}
console.log('],')
}


const generateLayout = (): Layout => {
const layout = [];

for(var i = 0; i < 9; i++) {
const r = Math.floor(generateNextRandom() * 8)
const rr = Math.floor(generateNextRandom() * 8) + 2;

if (r == 0) {
layout.push([[0, 0], [0, 0], [0, 0]])
}

if (r == 1) {
if (i == 0) {
layout.push([[1, 9], [0, 0], [0, 0]])
}
else {
layout.push([[i * 10, 10 + Number(i == 8)], [0, 0], [0, 0]])
}
}

if (r == 2) {
if (i == 0) {
layout.push([[0, 0], [1, 9], [0, 0]])
}
else {
layout.push([[0, 0], [i * 10, 10 + Number(i == 8)], [0, 0]])
}
}

if (r == 3) {
if (i == 0) {
layout.push([[0, 0], [0, 0], [1, 9]])
}
else {
layout.push([[0, 0], [0, 0], [i * 10, 10 + Number(i == 8)]])
}
}

if (r == 4) {
if (i == 0) {
layout.push([[1, rr - 1], [rr, 10 - rr], [0, 0]])
}
else {
layout.push([[i * 10, rr], [i * 10 + rr, 10 - rr + Number(i == 8)], [0, 0]])
}
}

if (r == 5) {
if (i == 0) {
layout.push([[0, 0], [1, rr - 1], [rr, 10 - rr]])
}
else {
layout.push([[0, 0], [i * 10, rr ], [i * 10 + rr, 10 - rr + Number(i == 8)]])
}
}

if (r == 6) {
if (i == 0) {
layout.push([[1, rr - 1], [0, 0], [rr, 10 - rr]])
}
else {
layout.push([[i * 10, rr], [0, 0], [i * 10 + rr, 10 - rr + Number(i == 8)]])
}
}

if (r == 7) {
if(i == 0) {
layout.push([[1, 3], [4, 3], [7, 3]])
}
else if(i == 8) {
layout.push([[80, 4], [84, 4], [88, 3]])
}
else {
layout.push([[i * 10, 3], [i * 10 + 3, 3], [i * 10 + 6, 4]])
}
}
}
return layout
}

const fixBitLength = (bits: String, len: number): String => {

let fixedBits = bits.split('').reverse().join('')
const numberOfZeroes = len - bits.length > 0 ? len - bits.length : 0
fixedBits += '0'.repeat(numberOfZeroes)
return fixedBits
}

const generateBitsFromLayout = (layout: Layout): String => {
let bits = ''

for(var i = 0; i < 3; i++) {
for(var j = 0; j < 9; j++) {
if(layout && layout[j] && layout[j][i] && layout[j][i][0] && layout[j][i][0] != 0) {

bits += fixBitLength(i.toString(2), 2) // first two bit is row number
bits += fixBitLength(j.toString(2), 4) // four bit for column number
bits += fixBitLength(layout[j][i][0].toString(2), 7) // seven bit for range start
bits += fixBitLength(layout[j][i][1].toString(2), 4) // four bit for range

}
}
}

return bits.split('').reverse().join('');
}

const generateBigNumberFromBits = (bits: String): BigNumber => {
let number = BigNumber.from('1') // for leading zeroes

for(var i = 0; i < bits.length; i++) {
number = number.mul(2)
if(bits[i] == '1') {
number = number.add(1)
}
//console.log(bits[i] == '1')
}

return number
}

const generateCardFromLayout = (layout: BigNumber, tokenSeed: number): number[][] => {
let card = []
for(var i = 0; i < 3; i++) {
card.push([0,0,0,0,0,0,0,0,0])
}

for(var i = 0; i < 15; i++) {
const row = Number(layout.mod(4)) // 2 bit
layout = layout.div(4)
const column = Number(layout.mod(16)) // 4 bit
layout = layout.div(16)
const floorNumber = Number(layout.mod(128)) // 7 bit
layout = layout.div(128)
const range = Number(layout.mod(16)) // 4 bit
layout = layout.div(16)
card[row][column] = (floorNumber + tokenSeed % range)
}

return card
}


function main() {
let counter = 0;
const layouts: Array<Layout> = [];
const bits: Array<String> = [];
const numbers: Array<BigNumber> = [];
console.log("Layouts: \n")

while(counter < 10) {
const layout: Layout = generateLayout()
if(isValidLayout(layout) && !layouts?.includes(layout)) {
layouts.push(layout)
counter += 1
layoutWriter(layout)

const bit: String = generateBitsFromLayout(layout)
bits.push(bit) // first 17 character for last number (4 for range, 7 for range start, 4 for column, 2 for row) !reversed

const layoutNumber: BigNumber = generateBigNumberFromBits(bit)
numbers.push(layoutNumber)
}
}
console.log("\nBinary forms of layouts: \n")

bits.map((b) => {
console.log(b, '\n')
})

console.log("\nNumber forms of layouts: \n");
numbers.map((number) => {
console.log(number.toString())
})

console.log('\nTest Card: \n', generateCardFromLayout(numbers[0], 128739456)) // layout, tokenSeed
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please print test cards for each layout.

// console.log(Number(generateBigNumberFromBits("001"))) // expected 9
}

main()
Loading