Created
November 27, 2022 14:08
-
-
Save ezynda3/e7a8f720d2cb349e1dc52e31dd707ef4 to your computer and use it in GitHub Desktop.
Connect Four Solidity
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// SPDX-License-Identifier: MIT | |
// | |
// Let's play connect-four! | |
// Check out the contract for details. | |
pragma solidity 0.8.17; | |
contract ConnectFour { | |
mapping(bytes32 => Game) public games; // Game IDs ==> Games | |
mapping(address => uint256) public nrOfGames; // User ==> Nr. of Games | |
mapping(address => mapping(uint256 => Game)) public myGames; // User ==> uint[0,1,..] ==> Game | |
mapping(address => mapping(address => Game)) public openGame; // User ==> Opponent ==> Game | |
struct Game { | |
bytes32 gameID; // unique id | |
address player1; // P1 | |
address player2; // P2 | |
bool alt; // false if its player1's turn | |
bool started; // true if P2 joined after being challenged | |
bool ended; // true if game ended | |
uint8[7][5] board; // 7x5 board | |
uint256 lastmove; // timestamp of last move | |
address draw; // draw proposals | |
} | |
event GameCreation( | |
address indexed p1, | |
address indexed p2, | |
bytes32 indexed game | |
); | |
event GameJoin( | |
address indexed p2, | |
address indexed p1, | |
bytes32 indexed game | |
); | |
event Move(address indexed p, bytes32 indexed game, uint8 move); | |
event GameEnd( | |
address indexed winner, | |
address indexed loser, | |
bytes32 indexed game | |
); | |
event DrawProposed(address indexed proposer, bytes32 indexed game); | |
/// Check that only players of a specific board are registered | |
modifier onlyGamers(bytes32 id) { | |
require( | |
games[id].player1 == msg.sender || games[id].player2 == msg.sender, | |
"No Game initiated" | |
); | |
_; | |
} | |
/// Check if game has started and p2 has supplied his pot | |
modifier startedGames(bytes32 id) { | |
require(games[id].started == true, "Game not started"); | |
_; | |
} | |
/** | |
* @notice Join game of host. | |
* @param victim Address of person to challenge for a game. | |
* @return gameID The Id of the game | |
*/ | |
function challangevictim(address victim) public returns (bytes32 gameID) { | |
require(msg.sender != victim, "Cannot challenge yourself"); | |
require( | |
openGame[msg.sender][victim].gameID == bytes32(0), | |
"Game already initiated" | |
); | |
gameID = keccak256( | |
abi.encodePacked(msg.sender, victim, block.timestamp) | |
); | |
Game memory game; | |
game.gameID = gameID; | |
game.player1 = msg.sender; | |
game.player2 = victim; | |
game.alt = true; | |
games[gameID] = game; | |
myGames[msg.sender][nrOfGames[msg.sender]] = game; | |
openGame[msg.sender][victim] = game; | |
nrOfGames[msg.sender] += 1; | |
emit GameCreation(msg.sender, victim, gameID); | |
return gameID; | |
} | |
/** | |
* @notice Join game of host. | |
* @param host Address of the challenger. | |
* @return gameID The Id of the game | |
*/ | |
function joinGame(address host) public returns (bytes32 gameID) { | |
require( | |
openGame[host][msg.sender].gameID != bytes32(0), | |
"Game not open" | |
); | |
require( | |
openGame[host][msg.sender].player2 == msg.sender, | |
"Not invited" | |
); | |
Game memory game = openGame[host][msg.sender]; | |
game.started = true; | |
games[game.gameID] = game; | |
myGames[msg.sender][nrOfGames[msg.sender]] = game; | |
uint256 len = nrOfGames[host]; | |
for (uint256 i = 0; i < len; i++) { | |
if (myGames[host][i].player2 == msg.sender) { | |
myGames[host][i] = game; | |
break; | |
} | |
} | |
nrOfGames[msg.sender] += 1; | |
emit GameJoin(msg.sender, host, game.gameID); | |
return gameID; | |
} | |
/** | |
* @notice Retrieve board of game with specified ID. | |
* @param gameID The ID of the game. | |
* @return board Board and turn | |
* @return turn False if it's the turn of p1 | |
*/ | |
function getBoard(bytes32 gameID) | |
public | |
view | |
returns (uint8[7][5] memory board, string memory turn) | |
{ | |
if (games[gameID].alt == false) { | |
turn = "Player 1"; | |
} else { | |
turn = "Player 2"; | |
} | |
return (games[gameID].board, turn); | |
} | |
/** | |
* @notice Retrieve game with specified ID. | |
* @param gameID The ID of the game. | |
* @return p1 Address of player 1. | |
* @return p2 Address of player 2. | |
* @return turn False if it's the turn of p1. | |
* @return ended Bool indicating if the game ended. | |
*/ | |
function getGame(bytes32 gameID) | |
public | |
view | |
returns ( | |
address p1, | |
address p2, | |
bool turn, | |
bool ended | |
) | |
{ | |
return ( | |
games[gameID].player1, | |
games[gameID].player2, | |
games[gameID].alt, | |
games[gameID].ended | |
); | |
} | |
/** | |
* @notice Place Stone in one of 0-7 columns. | |
* @param gameID The ID of the game. | |
* @param move The column where to place the stone. | |
*/ | |
function takeMove(bytes32 gameID, uint8 move) | |
public | |
onlyGamers(gameID) | |
startedGames(gameID) | |
{ | |
require(games[gameID].ended == false, "Game already ended"); | |
require(move < 8, "Only 7 columns"); | |
uint8 _player; | |
Game storage game = games[gameID]; | |
if (game.player1 == msg.sender) { | |
require(game.alt == false, "Other player's turn"); | |
_player = 1; | |
game.alt = true; | |
} else { | |
require(game.alt == true, "Other player's turn"); | |
_player = 2; | |
game.alt = false; | |
} | |
uint8 rowcount = 0; | |
while (game.board[rowcount][move] != 0) { | |
rowcount += 1; | |
} | |
game.board[rowcount][move] = _player; | |
emit Move(msg.sender, gameID, move); | |
checkWin(gameID); | |
} | |
/** | |
* @notice Claim win for specific game. | |
* @dev The function checks if a player won and sends | |
* the pot to the winner. | |
* @param gameID The ID of the game. | |
* @return win Returns true if own. | |
*/ | |
function checkWin(bytes32 gameID) internal returns (bool win) { | |
uint8 _player; | |
address loser; | |
Game storage g = games[gameID]; | |
if (g.player1 == msg.sender) { | |
_player = 1; | |
loser = g.player2; | |
} else { | |
_player = 2; | |
loser = g.player1; | |
} | |
// horizontal | |
for (uint256 i = 0; i < 5; i++) { | |
for (uint256 j = 0; j < 4; j++) { | |
if ( | |
g.board[i][j] == _player && | |
g.board[i][j + 1] == _player && | |
g.board[i][j + 2] == _player && | |
g.board[i][j + 3] == _player | |
) { | |
g.ended = true; | |
} | |
} | |
} | |
// vertical | |
for (uint256 i = 0; i < 7; i++) { | |
for (uint256 j = 0; j < 2; j++) { | |
if ( | |
g.board[j][i] == _player && | |
g.board[j + 1][i] == _player && | |
g.board[j + 2][i] == _player && | |
g.board[j + 3][i] == _player | |
) { | |
g.ended = true; | |
} | |
} | |
} | |
// ascending - diagonal | |
for (uint256 i = 0; i < 2; i++) { | |
for (uint256 j = 0; j < 4; j++) { | |
if ( | |
g.board[i][j] == _player && | |
g.board[i + 1][j + 1] == _player && | |
g.board[i + 2][j + 2] == _player && | |
g.board[i + 3][j + 3] == _player | |
) { | |
g.ended = true; | |
} | |
} | |
} | |
// descending - diagonal | |
for (uint256 i = 0; i < 2; i++) { | |
for (uint256 j = 0; j < 4; j++) { | |
if ( | |
g.board[i + 3][j] == _player && | |
g.board[i + 2][j + 1] == _player && | |
g.board[i + 1][j + 2] == _player && | |
g.board[i][j + 3] == _player | |
) { | |
g.ended = true; | |
} | |
} | |
} | |
if (g.ended == true) { | |
openGame[g.player1][g.player2].gameID = bytes32(0); | |
for (uint256 i = 0; i < nrOfGames[g.player1]; i++) { | |
if (myGames[g.player1][i].gameID == g.gameID) { | |
myGames[g.player1][i].ended = true; | |
break; | |
} | |
} | |
for (uint256 i = 0; i < nrOfGames[g.player2]; i++) { | |
if (myGames[g.player2][i].gameID == g.gameID) { | |
myGames[g.player2][i].ended = true; | |
break; | |
} | |
} | |
emit GameEnd(msg.sender, loser, gameID); | |
return true; | |
} | |
return false; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Forked and modified from https://github.com/Nerolation/ethereum-four-in-a-row