“Chaseopoly” is a fully-featured custom implementation of the classic Monopoly board game, themed around Chase School subjects and locations. The application provides a complete digital board game experience with multiplayer support (2-4 players), property management, chance/community chest cards, and comprehensive game mechanics including jail, taxes, and utilities.
monopoly/
└── index.html # Complete Monopoly game implementation (801 lines)
.board {
position: relative;
width: 100%;
aspect-ratio: 1; /* Perfect square board */
background: #fafafa;
border: 2px solid #e0e0e0;
display: grid;
grid-template-columns: repeat(11, 1fr); /* 11x11 grid system */
grid-template-rows: repeat(11, 1fr);
}
/* Corner spaces (larger, 2x2 grid cells) */
.corner {
grid-column: span 2;
grid-row: span 2;
font-size: 11px;
font-weight: 600;
}
/* Precise board positioning classes */
.go { grid-column: 11; grid-row: 11; }
.jail { grid-column: 1; grid-row: 11; }
.free-parking { grid-column: 1; grid-row: 1; }
.go-to-jail { grid-column: 11; grid-row: 1; }
/* Side positioning (bottom, left, top, right) */
.bottom-1 { grid-column: 10; grid-row: 11; }
.left-1 { grid-column: 1; grid-row: 10; }
.top-1 { grid-column: 2; grid-row: 1; }
.right-1 { grid-column: 11; grid-row: 2; }
The game features properties themed around Chase School subjects and locations:
const boardSpaces = [
{ name: 'GO', type: 'go', position: 0 },
{ name: 'Dance Studio', type: 'property', color: 'brown', price: 60, rent: 2 },
{ name: 'Music', type: 'property', color: 'brown', price: 60, rent: 4 },
{ name: 'Creative Media Corridor', type: 'station', price: 200, rent: 25 },
{ name: 'Performing Arts', type: 'property', color: 'light-blue', price: 100, rent: 6 },
{ name: 'Sociology', type: 'property', color: 'light-blue', price: 100, rent: 6 },
{ name: 'Psychology', type: 'property', color: 'light-blue', price: 120, rent: 8 },
// ... continues with all Chase School subjects and locations
{ name: 'COOP', type: 'property', color: 'dark-blue', price: 400, rent: 50 }
];
/* Property color coding by subject area */
.brown { background: #8B4513; } /* Arts subjects */
.light-blue { background: #5DADE2; } /* Social sciences */
.pink { background: #EC7063; } /* Creative subjects */
.orange { background: #F39C12; } /* Humanities */
.red { background: #E74C3C; } /* Mathematics */
.yellow { background: #F1C40F; } /* Languages & Computing */
.green { background: #27AE60; } /* Sciences */
.dark-blue { background: #2C3E50; } /* Local businesses */
const player = {
id: index + 1,
name: 'Player Name',
money: 1500, /* Starting money */
position: 0, /* Board position (0-39) */
properties: [], /* Owned properties array */
color: `player${index + 1}`, /* CSS class for token color */
inJail: false, /* Jail status */
jailTurns: 0 /* Turns spent in jail */
};
const gameState = {
players: [], /* Array of player objects */
currentPlayer: 0, /* Index of current player */
spaces: [], /* Board spaces with ownership data */
started: false, /* Game initialization status */
modalCallback: null /* Current modal interaction */
};
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
background: #f5f5f5; /* Light gray background */
display: flex;
justify-content: center;
align-items: center;
color: #222;
}
.game-container {
background: white;
padding: 40px;
max-width: 1200px;
width: 95%;
display: flex;
gap: 40px; /* Board and controls separation */
}
.space {
border: 1px solid #e0e0e0;
background: white;
position: relative;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 9px;
text-align: center;
padding: 2px;
cursor: pointer;
transition: transform 0.2s;
}
.space:hover {
transform: scale(1.05); /* Subtle hover effect */
z-index: 10;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.property {
position: relative;
}
.color-bar {
position: absolute;
top: 0;
left: 0;
right: 0;
height: 15%; /* Color indicator at top of property */
}
function positionToken(playerIndex, position) {
const token = document.getElementById(`player${playerIndex + 1}`);
const spaces = document.querySelectorAll('.space');
const targetSpace = spaces[position];
if (targetSpace) {
const rect = targetSpace.getBoundingClientRect();
const boardRect = document.getElementById('board').getBoundingClientRect();
// Multiple player offset calculation
const offsetX = (playerIndex % 2) * 15 - 7.5; /* Horizontal offset */
const offsetY = Math.floor(playerIndex / 2) * 15 - 7.5; /* Vertical offset */
token.style.left = `${rect.left - boardRect.left + rect.width/2 - 9 + offsetX}px`;
token.style.top = `${rect.top - boardRect.top + rect.height/2 - 9 + offsetY}px`;
}
}
.player-token {
position: absolute;
width: 18px;
height: 18px;
border-radius: 50%;
transition: all 0.5s ease; /* Smooth movement animation */
z-index: 20;
border: 2px solid white;
}
/* Player color system */
.player1 { background: #E74C3C; } /* Red */
.player2 { background: #3498DB; } /* Blue */
.player3 { background: #2ECC71; } /* Green */
.player4 { background: #F39C12; } /* Orange */
function rollDice() {
const dice1 = Math.floor(Math.random() * 6) + 1;
const dice2 = Math.floor(Math.random() * 6) + 1;
const total = dice1 + dice2;
document.getElementById('dice1').textContent = dice1;
document.getElementById('dice2').textContent = dice2;
const currentPlayer = gameState.players[gameState.currentPlayer];
// Handle jail mechanics
if (currentPlayer.inJail) {
if (dice1 === dice2) {
// Doubles get out of jail free
currentPlayer.inJail = false;
currentPlayer.jailTurns = 0;
} else {
currentPlayer.jailTurns++;
if (currentPlayer.jailTurns >= 3) {
// Forced payment after 3 turns
currentPlayer.money -= 50;
currentPlayer.inJail = false;
currentPlayer.jailTurns = 0;
} else {
nextTurn();
return;
}
}
}
movePlayer(total);
}
function movePlayer(spaces) {
const currentPlayer = gameState.players[gameState.currentPlayer];
const oldPosition = currentPlayer.position;
currentPlayer.position = (currentPlayer.position + spaces) % 40;
// Pass GO bonus detection
if (currentPlayer.position < oldPosition) {
currentPlayer.money += 200; /* Collect £200 for passing GO */
}
positionToken(gameState.currentPlayer, currentPlayer.position);
setTimeout(() => {
handleLanding(); /* Process landing after animation */
}, 500);
}
function handleProperty(space) {
const currentPlayer = gameState.players[gameState.currentPlayer];
if (!space.owner) {
// Property available for purchase
if (currentPlayer.money >= space.price) {
showModal(
`Buy ${space.name}?`,
`Price: £${space.price}`,
() => {
// Purchase confirmed
currentPlayer.money -= space.price;
space.owner = currentPlayer.id;
currentPlayer.properties.push(space);
updateUI();
hideModal();
nextTurn();
},
() => {
// Purchase declined
hideModal();
nextTurn();
}
);
}
} else if (space.owner !== currentPlayer.id) {
// Pay rent to another player
const owner = gameState.players.find(p => p.id === space.owner);
let rent = space.rent;
// Special utility rent calculation
if (space.type === 'utility') {
const dice1 = parseInt(document.getElementById('dice1').textContent);
const dice2 = parseInt(document.getElementById('dice2').textContent);
rent = (dice1 + dice2) * 4; /* Rent = dice roll × 4 */
}
currentPlayer.money -= rent;
owner.money += rent;
showModal(
'Rent Due',
`Pay £${rent} to ${owner.name}`,
() => {
hideModal();
nextTurn();
}
);
}
}
function handleChance() {
const chances = [
{
text: 'Advance to GO',
action: (player) => {
player.position = 0;
player.money += 200; /* Collect GO bonus */
positionToken(gameState.currentPlayer, 0);
}
},
{
text: 'Bank pays dividend of £50',
action: (player) => {
player.money += 50;
}
},
{
text: 'Pay school fees of £150',
action: (player) => {
player.money -= 150;
}
}
];
const chance = chances[Math.floor(Math.random() * chances.length)];
const currentPlayer = gameState.players[gameState.currentPlayer];
showModal('Chance', chance.text, () => {
chance.action(currentPlayer);
hideModal();
updateUI();
nextTurn();
});
}
function handleCommunityChest() {
const chests = [
{ text: 'Collect £100', action: (player) => player.money += 100 },
{ text: 'Pay £50', action: (player) => player.money -= 50 },
{ text: 'Collect £10', action: (player) => player.money += 10 }
];
const chest = chests[Math.floor(Math.random() * chests.length)];
const currentPlayer = gameState.players[gameState.currentPlayer];
showModal('Community Chest', chest.text, () => {
chest.action(currentPlayer);
hideModal();
updateUI();
nextTurn();
});
}
function showModal(title, text, onYes, onNo) {
document.getElementById('modalTitle').textContent = title;
document.getElementById('modalText').textContent = text;
gameState.modalCallback = { yes: onYes, no: onNo || hideModal };
const modal = document.getElementById('modal');
modal.style.display = 'flex';
// Dynamic button configuration
if (!onNo) {
// Single button (OK only)
modal.querySelector('.modal-buttons').innerHTML =
'<button class="btn" onclick="modalYes()">OK</button>';
} else {
// Two buttons (Yes/No)
modal.querySelector('.modal-buttons').innerHTML = `
<button class="btn" onclick="modalYes()">Yes</button>
<button class="btn" onclick="modalNo()">No</button>
`;
}
}
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.3); /* Semi-transparent overlay */
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background: white;
padding: 30px;
max-width: 400px;
width: 90%;
text-align: center;
}
function goToJail() {
const currentPlayer = gameState.players[gameState.currentPlayer];
currentPlayer.position = 10; /* Jail position */
currentPlayer.inJail = true;
currentPlayer.jailTurns = 0;
positionToken(gameState.currentPlayer, 10);
}
// Jail escape mechanics in rollDice()
if (currentPlayer.inJail) {
if (dice1 === dice2) {
// Doubles: immediate release
currentPlayer.inJail = false;
currentPlayer.jailTurns = 0;
} else {
currentPlayer.jailTurns++;
if (currentPlayer.jailTurns >= 3) {
// Forced payment after 3 failed attempts
currentPlayer.money -= 50;
currentPlayer.inJail = false;
currentPlayer.jailTurns = 0;
}
}
}
function updateUI() {
const currentPlayer = gameState.players[gameState.currentPlayer];
document.getElementById('currentPlayerName').textContent = currentPlayer.name;
document.getElementById('playerMoney').textContent = currentPlayer.money;
document.getElementById('playerPosition').textContent =
gameState.spaces[currentPlayer.position].name;
updatePropertyList();
}
function updatePropertyList() {
const currentPlayer = gameState.players[gameState.currentPlayer];
const propertyList = document.getElementById('propertyList');
propertyList.innerHTML = '';
currentPlayer.properties.forEach(property => {
const item = document.createElement('div');
item.className = 'property-item';
item.textContent = `${property.name} - £${property.price}`;
propertyList.appendChild(item);
});
}
.controls {
flex: 0 0 350px; /* Fixed width sidebar */
display: flex;
flex-direction: column;
gap: 30px;
}
.player-setup, .player-info, .dice-container, .property-list {
background: #fafafa;
padding: 20px;
border: 1px solid #e0e0e0;
}
.dice {
display: inline-block;
width: 50px;
height: 50px;
background: white;
border: 2px solid #333;
margin: 10px;
font-size: 28px;
line-height: 46px;
font-weight: 600;
}
function createBoard() {
const board = document.getElementById('board');
const positions = [
'go', 'bottom-1', 'bottom-2', 'bottom-3', 'bottom-4', 'bottom-5',
'bottom-6', 'bottom-7', 'bottom-8', 'bottom-9', 'jail',
// ... all 40 positions
];
boardSpaces.forEach((space, index) => {
const spaceElement = document.createElement('div');
spaceElement.className = `space ${positions[index]}`;
// Corner space styling
if ([0, 10, 20, 30].includes(index)) {
spaceElement.classList.add('corner');
}
// Property color bar
if (space.type === 'property' || space.type === 'station') {
spaceElement.classList.add('property');
if (space.color) {
const colorBar = document.createElement('div');
colorBar.className = `color-bar ${space.color}`;
spaceElement.appendChild(colorBar);
}
}
// Space name and price
const nameDiv = document.createElement('div');
nameDiv.textContent = space.name;
nameDiv.style.fontSize = index % 10 === 0 ? '11px' : '9px';
spaceElement.appendChild(nameDiv);
if (space.price) {
const priceDiv = document.createElement('div');
priceDiv.textContent = `£${space.price}`;
priceDiv.style.fontSize = '8px';
priceDiv.style.color = '#666';
spaceElement.appendChild(priceDiv);
}
board.appendChild(spaceElement);
});
}
function nextTurn() {
gameState.currentPlayer = (gameState.currentPlayer + 1) % gameState.players.length;
updateUI();
}
function handleLanding() {
const currentPlayer = gameState.players[gameState.currentPlayer];
const space = gameState.spaces[currentPlayer.position];
updateUI();
switch (space.type) {
case 'property':
case 'station':
case 'utility':
handleProperty(space);
break;
case 'tax':
currentPlayer.money -= space.amount;
nextTurn();
break;
case 'gotojail':
goToJail();
nextTurn();
break;
case 'chance':
handleChance();
break;
case 'chest':
handleCommunityChest();
break;
default:
nextTurn();
}
}
Chaseopoly represents an excellent digital implementation of the classic Monopoly board game with a creative educational theme. The sophisticated CSS Grid layout, comprehensive game mechanics, and smooth player interaction create an engaging and fully functional board game experience.
Technical Rating: 8.9/10
The application successfully demonstrates how classic board games can be effectively translated to digital formats while maintaining educational value and engaging gameplay mechanics.