Skip to content

Instantly share code, notes, and snippets.

@ezynda3
Created November 27, 2022 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save ezynda3/e7a8f720d2cb349e1dc52e31dd707ef4 to your computer and use it in GitHub Desktop.
Save ezynda3/e7a8f720d2cb349e1dc52e31dd707ef4 to your computer and use it in GitHub Desktop.
Connect Four Solidity
// 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;
}
}
@ezynda3
Copy link
Author

ezynda3 commented Nov 27, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment