This repository has been archived by the owner on Sep 27, 2019. It is now read-only.
/
TicTacToeGame.sol
219 lines (166 loc) · 9.31 KB
/
TicTacToeGame.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
pragma solidity ^0.4.18;
import "fmg-core/contracts/State.sol";
import "./TicTacToeState.sol";
import { TicTacToeHelpers } from "./TicTacToeHelpers.sol";
contract TicTacToeGame {
using TicTacToeState for bytes;
// NOTE ON ERROR MESSAGES:
// require() messages we will incur n * 20,000 gas for each error message used, where n is the number of 32 byte slots it takes up
// https://github.com/ethereum/solidity/issues/4588
// This gas cost of deploying this contract is close the typical block gas limit, implying that it should be deployed in
// several smaller stages if we want to have require() messages.
// The following transitions are allowed:
// XPlaying -> OPlaying
// XPlaying -> Victory
// NB: We cannot transition from XPlaying to Draw (X is always completing the board because X goes first) remember we are transitioning *from* Xplay, so noughts are making the new marks
// OPlaying -> XPlaying
// OPlaying -> Victory
// OPlaying -> Draw
// Victory -> PlayAgainMeFirst
//
// Draw -> PlayAgainMeFirst
//
// PlayAgainMeFirst -> PlayAgainMeSecond
//
// PlayAgainMeSecond -> XPlaying
modifier destinationUnchanged(bytes _old, bytes _new) {
// If running on Nitro Adjudicator, this modifier should be filled out accordingly
_;
}
function validTransition(bytes _old, bytes _new)
public
destinationUnchanged(_old,_new)
pure
returns (bool) {
if (_old.positionType() == TicTacToeState.PositionType.XPlaying) {
if (_new.positionType() == TicTacToeState.PositionType.OPlaying) {
validateXPlayingToOPlaying(_old, _new);
return true;
} else if (_new.positionType() == TicTacToeState.PositionType.Victory) {
validateXPlayingToVictory(_old, _new);
return true;
}
} else if (_old.positionType() == TicTacToeState.PositionType.OPlaying) {
if (_new.positionType() == TicTacToeState.PositionType.XPlaying) {
validateOPlayingToXPlaying(_old, _new);
return true;
} else if (_new.positionType() == TicTacToeState.PositionType.Victory) {
validateOPlayingToVictory(_old, _new);
return true;
} else if (_new.positionType() == TicTacToeState.PositionType.Draw) {
validateOPlayingToDraw(_old, _new);
return true;
}
} else if (_old.positionType() == TicTacToeState.PositionType.Victory) {
if (_new.positionType() == TicTacToeState.PositionType.PlayAgainMeFirst) {
validateVictoryToPlayAgainMeFirst(_old, _new);
return true;
}
// TODO consider allowing a transition to PlayAgainMeSecond, to allow the loser to forfeit their right to go first
} else if (_old.positionType() == TicTacToeState.PositionType.Draw) {
if (_new.positionType() == TicTacToeState.PositionType.PlayAgainMeFirst) {
validateDrawToPlayAgainMeFirst(_old, _new);
return true;
}
} else if (_old.positionType() == TicTacToeState.PositionType.PlayAgainMeFirst) {
if (_new.positionType() == TicTacToeState.PositionType.PlayAgainMeSecond) {
validatePlayAgainMeFirstToPlayAgainMeSecond(_old, _new);
return true;
}
} else if (_old.positionType() == TicTacToeState.PositionType.PlayAgainMeSecond) {
if (_new.positionType() == TicTacToeState.PositionType.XPlaying) {
validatePlayAgainMeSecondToXPlaying(_old, _new);
return true;
}
}
revert("Could not match to a valid transition.");
// return false;
}
// transition validations
function validateXPlayingToOPlaying(bytes _old, bytes _new) private pure {
require(_new.stake() == _old.stake());
require(TicTacToeHelpers.madeStrictlyOneMark(_new.noughts(), _old.noughts()));
require((_new.crosses() == _old.crosses()));
if (State.indexOfMover(_new) == 0) { // mover is A
require(_new.aResolution() == _old.aResolution() + 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() - 2 * _new.stake());
} else if (State.indexOfMover(_new) == 1) { // mover is B
require(_new.aResolution() == _old.aResolution() - 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() + 2 * _new.stake());
// note factor of 2 to swing fully to other player
}
}
function validateXPlayingToVictory(bytes _old, bytes _new) private pure {
require(TicTacToeHelpers.hasWon(_new.noughts()));
require(TicTacToeHelpers.madeStrictlyOneMark(_new.noughts(), _old.noughts()));
require((_new.crosses() == _old.crosses()));
if (State.indexOfMover(_new) == 0) { // mover is A
require(_new.aResolution() == _old.aResolution() + 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() - 2 * _new.stake());
} else if (State.indexOfMover(_new) == 1) { // mover is B
require(_new.aResolution() == _old.aResolution() - 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() + 2 * _new.stake());
} // mover gets to claim stakes
}
function validateOPlayingToXPlaying(bytes _old, bytes _new) private pure {
require(TicTacToeHelpers.madeStrictlyOneMark(_new.crosses(), _old.crosses()));
require((_new.noughts() == _old.noughts()));
if (State.indexOfMover(_new) == 0) { // mover is A
require(_new.aResolution() == _old.aResolution() + 2 * _new.stake()); // note extra factor of 2 to swing fully to other player
require(_new.bResolution() == _old.bResolution() - 2 * _new.stake());
} else if (State.indexOfMover(_new) == 1) { // mover is B
require(_new.aResolution() == _old.aResolution() - 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() + 2 * _new.stake());
} // mover gets to claim stakes: note factor of 2 to swing fully to other player
}
function validateOPlayingToVictory(bytes _old, bytes _new) private pure {
require(TicTacToeHelpers.hasWon(_new.crosses()));
require(TicTacToeHelpers.madeStrictlyOneMark(_new.crosses(), _old.crosses()));
require((_new.noughts() == _old.noughts()));
if (State.indexOfMover(_new) == 0) { // mover is A
require(_new.aResolution() == _old.aResolution() + 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() - 2 * _new.stake());
} else if (State.indexOfMover(_new) == 1) { // mover is B
require(_new.aResolution() == _old.aResolution() - 2 * _new.stake());
require(_new.bResolution() == _old.bResolution() + 2 * _new.stake());
} // mover gets to claim stakes: note factor of 2 to swing fully to other player
}
function validateOPlayingToDraw(bytes _old, bytes _new) private pure {
require(TicTacToeHelpers.isDraw(_new.noughts(), _new.crosses())); // check if board full.
// crosses always plays first move and always plays the move that completes the board
if (State.indexOfMover(_new) == 0) {
require(_new.aResolution() == _old.aResolution() + 1 * _new.stake()); // no extra factor of 2, restoring to parity
require(_new.bResolution() == _old.bResolution() - 1 * _new.stake());
} else if (State.indexOfMover(_new) == 1) {
require(_new.aResolution() == _old.aResolution() - 1 * _new.stake());
require(_new.bResolution() == _old.bResolution() + 1 * _new.stake());
} // mover gets to restore parity to the winnings
require(TicTacToeHelpers.madeStrictlyOneMark(_new.crosses(), _old.crosses()));
require((_new.noughts() == _old.noughts()));
}
function validateVictoryToPlayAgainMeFirst(bytes _old, bytes _new) private pure {
require(_new.aResolution() == _old.aResolution());
require(_new.bResolution() == _old.bResolution());
}
function validateDrawToPlayAgainMeFirst(bytes _old, bytes _new) private pure {
require(_new.aResolution() == _old.aResolution());
require(_new.bResolution() == _old.bResolution());
}
function validatePlayAgainMeFirstToPlayAgainMeSecond(bytes _old, bytes _new) private pure {
require(_new.stake() == _old.stake());
require(_new.aResolution() == _old.aResolution());
require(_new.bResolution() == _old.bResolution());
}
function validatePlayAgainMeSecondToXPlaying(bytes _old, bytes _new) private pure {
require(_new.noughts() == 0);
require(TicTacToeHelpers.madeStrictlyOneMark(_new.crosses(),0)); // Xs moves first
require(_new.stake() == _old.stake());
if (State.indexOfMover(_new) == 0) { // mover is A
require(_new.aResolution() == _old.aResolution() + _new.stake());
require(_new.bResolution() == _old.bResolution() - _new.stake());
} else if (State.indexOfMover(_new) == 1) { // mover is B
require(_new.aResolution() == _old.aResolution() - _new.stake());
require(_new.bResolution() == _old.bResolution() + _new.stake());
}
}
}