-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathPyramidGame.sol
256 lines (213 loc) · 8.95 KB
/
PyramidGame.sol
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
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
////////////////////////////////////////////////////////////////
// see https://etherscan.io/address/0xC3c94e2d9A33AB18D5578BD63DfdaA3e0EA74a49#code
pragma solidity ^0.4.17;
contract PyramidGame
{
/////////////////////////////////////////////
// Game parameters
uint256 private constant BOTTOM_LAYER_BET_AMOUNT = 0.005 ether;
uint256 private adminFeeDivisor; // e.g. 100 means a 1% fee, 200 means a 0.5% fee
/////////////////////////////////////////////
// Game owner
address private administrator;
/////////////////////////////////////////////
// Pyramid grid data
//
// The uint32 is the coordinates.
// It consists of two uint16's:
// The x is the most significant 2 bytes (16 bits)
// The y is the least significant 2 bytes (16 bits)
// x = coordinates >> 16
// y = coordinates & 0xFFFF
// coordinates = (x << 16) | y
// x is a 16-bit unsigned integer
// y is a 16-bit unsigned integer
mapping(uint32 => address) public coordinatesToAddresses;
uint32[] public allBlockCoordinates;
// In the user interface, the rows of blocks will be
// progressively shifted more to the right, as y increases
//
// For example, these blocks in the contract's coordinate system:
// ______
// 2 |__A__|______
// /|\ 1 |__B__|__D__|______
// | 0 |__C__|__E__|__F__|
// y 0 1 2
//
// x -->
//
//
// Become these blocks in the user interface:
// __ ______
// /| __|__A__|___
// / __|__B__|__D__|___
// y |__C__|__E__|__F__|
//
// x -->
//
//
/////////////////////////////////////////////
// Address properties
mapping(address => uint256) public addressesToTotalWeiPlaced;
mapping(address => uint256) public addressBalances;
////////////////////////////////////////////
// Game Constructor
function PyramidGame() public
{
administrator = msg.sender;
adminFeeDivisor = 200; // Default fee is 0.5%
// The administrator gets a few free chat messages :-)
addressesToChatMessagesLeft[administrator] += 5;
// Set the first block in the middle of the bottom row
coordinatesToAddresses[uint32(1 << 15) << 16] = msg.sender;
allBlockCoordinates.push(uint32(1 << 15) << 16);
}
////////////////////////////////////////////
// Pyramid grid reading functions
function getBetAmountAtLayer(uint16 y) public pure returns (uint256)
{
// The minimum bet doubles every time you go up 1 layer
return BOTTOM_LAYER_BET_AMOUNT * (uint256(1) << y);
}
function isThereABlockAtCoordinates(uint16 x, uint16 y) public view returns (bool)
{
return coordinatesToAddresses[(uint32(x) << 16) | uint16(y)] != 0;
}
function getTotalAmountOfBlocks() public view returns (uint256)
{
return allBlockCoordinates.length;
}
////////////////////////////////////////////
// Pyramid grid writing functions
function placeBlock(uint16 x, uint16 y) external payable
{
// You may only place a block on an empty spot
require(!isThereABlockAtCoordinates(x, y));
// Add the transaction amount to the person's balance
addressBalances[msg.sender] += msg.value;
// Calculate the required bet amount at the specified layer
uint256 betAmount = getBetAmountAtLayer(y);
// If the block is at the lowest layer...
if (y == 0)
{
// There must be a block to the left or to the right of it
require(isThereABlockAtCoordinates(x-1, y) ||
isThereABlockAtCoordinates(x+1, y));
}
// If the block is NOT at the lowest layer...
else
{
// There must be two existing blocks below it:
require(isThereABlockAtCoordinates(x , y-1) &&
isThereABlockAtCoordinates(x+1, y-1));
}
// Subtract the bet amount from the person's balance
addressBalances[msg.sender] -= betAmount;
// Place the block
coordinatesToAddresses[(uint32(x) << 16) | y] = msg.sender;
allBlockCoordinates.push((uint32(x) << 16) | y);
// If the block is at the lowest layer...
if (y == 0)
{
// The bet goes to the administrator
addressBalances[administrator] += betAmount;
}
// If the block is NOT at the lowest layer...
else
{
// Calculate the administrator fee
uint256 adminFee = betAmount / adminFeeDivisor;
// Calculate the bet amount minus the admin fee
uint256 betAmountMinusAdminFee = betAmount - adminFee;
// Add the money to the balances of the people below
addressBalances[coordinatesToAddresses[(uint32(x ) << 16) | (y-1)]] += betAmountMinusAdminFee / 2;
addressBalances[coordinatesToAddresses[(uint32(x+1) << 16) | (y-1)]] += betAmountMinusAdminFee / 2;
// Give the admin fee to the admin
addressBalances[administrator] += adminFee;
}
// The new sender's balance must not have underflowed
// (this verifies that the sender has enough balance to place the block)
require(addressBalances[msg.sender] < (1 << 255));
// Give the sender their chat message rights
addressesToChatMessagesLeft[msg.sender] += uint32(1) << y;
// Register the sender's total bets placed
addressesToTotalWeiPlaced[msg.sender] += betAmount;
}
////////////////////////////////////////////
// Withdrawing balance
function withdrawBalance(uint256 amountToWithdraw) external
{
require(amountToWithdraw != 0);
// The user must have enough balance to withdraw
require(addressBalances[msg.sender] >= amountToWithdraw);
// Subtract the withdrawn amount from the user's balance
addressBalances[msg.sender] -= amountToWithdraw;
// Transfer the amount to the user's address
// If the transfer() call fails an exception will be thrown,
// and therefore the user's balance will be automatically restored
msg.sender.transfer(amountToWithdraw);
}
/////////////////////////////////////////////
// Chatbox data
struct ChatMessage
{
address person;
string message;
}
mapping(bytes32 => address) public usernamesToAddresses;
mapping(address => bytes32) public addressesToUsernames;
mapping(address => uint32) public addressesToChatMessagesLeft;
ChatMessage[] public chatMessages;
mapping(uint256 => bool) public censoredChatMessages;
/////////////////////////////////////////////
// Chatbox functions
function registerUsername(bytes32 username) external
{
// The username must not already be token
require(usernamesToAddresses[username] == 0);
// The address must not already have a username
require(addressesToUsernames[msg.sender] == 0);
// Register the new username & address combination
usernamesToAddresses[username] = msg.sender;
addressesToUsernames[msg.sender] = username;
}
function sendChatMessage(string message) external
{
// The sender must have at least 1 chat message allowance
require(addressesToChatMessagesLeft[msg.sender] >= 1);
// Deduct 1 chat message allowence from the sender
addressesToChatMessagesLeft[msg.sender]--;
// Add the chat message
chatMessages.push(ChatMessage(msg.sender, message));
}
function getTotalAmountOfChatMessages() public view returns (uint256)
{
return chatMessages.length;
}
function getChatMessageAtIndex(uint256 index) public view returns (address, bytes32, string)
{
address person = chatMessages[index].person;
bytes32 username = addressesToUsernames[person];
return (person, username, chatMessages[index].message);
}
// In case of chat messages with extremely rude or inappropriate
// content, the administrator can censor a chat message.
function censorChatMessage(uint256 chatMessageIndex) public
{
require(msg.sender == administrator);
censoredChatMessages[chatMessageIndex] = true;
}
/////////////////////////////////////////////
// Game ownership functions
function transferOwnership(address newAdministrator) external
{
require(msg.sender == administrator);
administrator = newAdministrator;
}
function setFeeDivisor(uint256 newFeeDivisor) external
{
require(msg.sender == administrator);
require(newFeeDivisor >= 20); // The fee may never exceed 5%
adminFeeDivisor = newFeeDivisor;
}
}