Tutoriais

Programando um joguinho de celular

Esse tutorial mostra como programar um simples jogo para celular.

Código html do jogo

Crie um arquivo chamado index.htm
<!doctype html>
<html>
<meta charset=UTF-8>
<meta name=viewport content='width=device-width'>
<link rel="stylesheet" href="styles.css">
<title>MineSweeper</title>
<body>
  <h1>MineSweeper</h1>
  <div id="mines" data-mines=""></div>
  <div id="grid"></div>
  <div id="text"></div><br>
  <button id="reset">Reset</button>
  <footer><div></div></footer>
</body>
<script src=script.js></script>
</html>

O arquivo css

body {
	background-color: #cacaca;
	text-align: center;
	margin: 30px;
	font-family: "Segoe Ui";
}

h1 {
	background-color: #101010;
	padding: 20px;
	font-family: "Courier";
	text-transform: uppercase;
	color: #ffffff;
	text-align: center;
}

footer {
	position: fixed;
	bottom: 0px;
	left: 0px;
	width: 100%;
}

footer div {
	margin:30px;
	background-color: #101010;
	padding: 30px;
}


#grid {
	margin-top: 12px;
	margin-bottom: 20px;
	display: grid;
	justify-content: center;
	grid-template-columns: repeat(3, 30px);
	grid-template-rows: repeat(3, 30px);
}

#mines {
	color: #555555;
	font-weight: bold;
	margin-bottom: 20px;
}

#reset {
	padding: 10px;
}

.cell {
	background-color: #999999;
	font-size: 0;
	border: 1px solid #222222;
	border-radius: 4px;
	padding: 8px;
}

.visible {
	font-size: 16px;
	background-color: #bbbbbb;
	outline: none;
	box-shadow: inset 0px 0px 5px #c1c1c1;
}

.flag {
	background-color: #444444;
}

.visible.mine {
	background-color: #ffffff;
}

O arquivo js

Na função resetBoard vai o código que desenha o jogo.
const grid = document.getElementById("grid");
var gameOver = false;

var resetBoard = function() {
	document.getElementById('text').innerHTML = '';

	grid.style.setProperty('grid-template-columns', 'repeat(10, 30px)');
	grid.style.setProperty('grid-template-rows', 'repeat(10, 30px)');

	grid.innerHTML = '';
	for (var i = 0; i < 100; i++) {
		const cell = document.createElement("button");
		cell.className = 'cell';

		grid.appendChild(cell);
	}
	cells = grid.getElementsByClassName("cell");
	for (var i = 0; i < cells.length; i++) {
		cells[i].className = 'cell';
		cells[i].innerHTML = '';
		cells[i].addEventListener('click', clickCell, false);
		cells[i].oncontextmenu = clickFlag;
	}
	gameOver = false;

	placeMines();
	placeNums();
};
As duas últimas linhas de dentro da função resetBoard chamam funções que colocam as bombas e os números no tabuleiro.
placeMines e placeNums são funções que serão descritas nesse tutorial.

O código abaixo deve estar no final do arquivo javascript. Ele vincula o botão com a função que desenha o tabuleiro.
Na linha seguinte o código chama/invoca ela (resetBoard).
document.getElementById('reset').addEventListener('click', resetBoard);
resetBoard();

getImmediateNeighbors

Essa função retorna as minas que existem nas adjacências.
var getImmediateNeighbors = function(cell) {
	const i = [...cells].indexOf(cell);
	let topRow = true;
	let bottomRow = false;
	const neighbors = [];

	if (i > 9) {
		topRow = false;
		neighbors.push(cells[i-10]);
	}
	if (i < 90) {
		neighbors.push(cells[i+10]);
	}
	else bottomRow = true;
	if (i % 10 != 0) {
		neighbors.push(cells[i-1]);
		if (!topRow) neighbors.push(cells[i-11]);
		if (!bottomRow) neighbors.push(cells[i+9]);
	}
	if (i % 10 != 9) {
		neighbors.push(cells[i+1]);
		if (!topRow) neighbors.push(cells[i-9]);
		if (!bottomRow) neighbors.push(cells[i+11]);
	}
	return neighbors;
};

placeMines & placeNums

A função placeMines distribui 20 minas pela grid.
var placeMines = function() {
	const cells = document.getElementsByClassName("cell");
	const minesI = [];

	// Get unique random indexes to place the mines
	mines_count = 20;
	const mines_text = document.getElementById("mines");
	mines_text.setAttribute("data-mines", mines_count);
	mines_text.innerHTML = mines_count + " mines left";
	hidden = 100;
	while (minesI.length < mines_count) {
		var I = Math.floor(Math.random() * cells.length);
		if (minesI.indexOf(I) === -1) minesI.push(I);
	}

	// Set the cells with mines according to the indexes in the minesI array
	for (var i = 0; i < minesI.length; i++) {
		cells[minesI[i]].classList.add("mine");
		cells[minesI[i]].innerHTML = "X";
	}
};
A função placeNums exibe a quantidade de bombas nas adjacências.
var placeNums = function() {
	for (var i = 0; i < cells.length; i++) {
		if (cells[i].classList.contains('mine')) continue;

		let neighbors = getImmediateNeighbors(cells[i]);
		let count = 0;
		for (var j = 0; j < neighbors.length; j++) {
			count += neighbors[j].classList.contains('mine');
		}

		cells[i].innerHTML = count > 0 ? count : '';
	}
};

clickFlag & clickCell

A função resetBoard atribui a chamada da função clickCell ao evento 'click' nas células/botões.
var clickCell = function() {
	if (gameOver) return;
	if (this.classList.contains('visible')) return;
	if (this.classList.contains('flag')) return;

	this.classList.add('visible');
	hidden = hidden - 1;

	if (this.classList.contains('mine')) {
		document.getElementById('text').innerHTML = "Game Over";
		gameOver = true;
		return;
	}

	if (hidden === mines_count) {
		document.getElementById('text').innerHTML = "You Won !";
		gameOver = true;
		return;
	}

	if (this.childNodes.length === 0) {
		let neighbors = getImmediateNeighbors(this);
		for (var i = 0; i < neighbors.length; i++) {
			neighbors[i].click();
		}
	}
};
A função resetBoard atribui a chamada da função clickFlag ao evento 'right click' nas células/botões. (cells[i].oncontextmenu=clickFlag)
var clickFlag = function() {
	if (gameOver) return;
	if (this.classList.contains('visible')) return;

	mines_text = document.getElementById("mines");
	const mines_count = Number(mines_text.getAttribute("data-mines"));
	if (this.classList.contains('flag')) {
		this.classList.remove('flag');
		mines_text.setAttribute("data-mines", mines_count + 1);
		mines_text.innerHTML = (mines_count + 1) + " mines left";
	}
	else {
		this.classList.add('flag');
		mines_text.setAttribute("data-mines", mines_count - 1);
		mines_text.innerHTML = (mines_count - 1) + " mines left";
	}

	return false;
};

Teste

Antes de colocarmos essa página no app é preciso confirmar que ela está funcionando legal.

Android Studio

Inicie um novo projeto.
Uma 'activity em branco' com o package name sendo campo.minado.

A pasta 'res'

Acesse a pasta do projeto
Encontre dentro dela a pasta 'res' e delete todas as pastas que estão dentro



Crie uma pasta chamada drawable e coloque dentro um 'png' que vai ser o icone do app.



Crie uma pasta raw (também dentro da /res/) e coloque dentro da /res/raw/ o arquivo html, o arquivo css e o arquivo js.

O arquivo manifest

O arquivo manifest é um arquivo de configuração.

É preciso setar (no manifest) o ícone, o 'tema' e rótulo
android:icon="@drawable/icon"
android:theme="@android:style/Theme.Light.NoTitleBar"
android:label="Campo Minado"

Compilando

Já é possível gerar um app mas por enquanto ele ainda não exibe os arquivos html

Arquivo código java

Falta incluir (no MainActivity) o código java que coloca a página no app
package tutorial.campominado;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.webkit.WebView;

public class MainActivity extends Activity {
	@Override protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		WebView gameView;
		gameView=new WebView(this);
		gameView.getSettings().setJavaScriptEnabled(true);
		gameView.loadUrl("file:///android_res/raw/index.htm");
		setContentView(gameView);
	}
}

Projeto Montado

Arquivos do projeto Campo Minado.