概要
友人に 本気で始めるiPhoneアプリ作り という本を頂いたので、Swiftでオセロゲームを作りました。このページで紹介するものはオフライン対戦です。サーバーとの通信を実装したものは他のページで紹介します。
環境
OS
・ macOs
開発環境
・ Xcode10
・ Swift4
完成画像
ファイル一覧
この 4 ~ 6 の画像を使ってオセロ盤を作ります。
難しいところはないと思うのでコメントを読んで理解してください。わからないところがあればコメントお願いします。記事を修正します。
board.swift
board.swift
import Foundation
class Board{
var SIZE: Int = 0
let DIRECTIONS_XY = [[-1, -1], [+0, -1], [+1, -1],
[-1, +0], [+1, +0],
[-1, +1], [+0, +1], [+1, +1]]
let BLACK = -1
let WHITE = 1
let BLANK = 0
var square:[[Int]] = []
// オセロを開始する際に呼ばれ、オセロ盤を初期化する
func start(size: Int){
self.SIZE = size
let center = size / 2
for _ in 0..<self.SIZE{
var array:[Int] = []
for _ in 0..<self.SIZE{
array += [BLANK]
}
square += [array]
}
square[center-1][center-1] = self.WHITE
square[center-1][center] = self.BLACK
square[center][center-1] = self.BLACK
square[center][center] = self.WHITE
}
// 盤上にある石の個数を返す
func returnStone() -> (Int,Int) {
var black = 0
var white = 0
var blank = 0
for y in 0..<SIZE{
for x in 0..<SIZE{
switch square[y][x]{
case BLACK:
black += 1
case WHITE:
white += 1
default:
blank += 1
}
}
}
return (black, white)
}
// 対戦終了時もう一度対戦する際にボード板をリセットする
func reset(){
var _square:[[Int]] = []
let size = SIZE
let center = size / 2
for _ in 0..<SIZE{
var array:[Int] = []
for _ in 0..<SIZE{
array += [BLANK]
}
_square += [array]
}
_square[center-1][center-1] = self.WHITE
_square[center-1][center] = self.BLACK
_square[center][center-1] = self.BLACK
_square[center][center] = self.WHITE
square = _square
}
// ボード盤を返す
func return_board() -> [[Int]]{
return square
}
// 呼ばれた段階で Game Over であるかどうかを判定する
func gameOver() -> Bool {
var black = 0
var white = 0
var blank = 0
for y in 0..<SIZE{
for x in 0..<SIZE{
switch square[y][x]{
case BLACK:
black += 1
case WHITE:
white += 1
default:
blank += 1
}
}
}
if( blank == 0 || black == 0 || white == 0 ){
return true
}
if( self.available(stone: BLACK).count == 0 && self.available(stone: WHITE).count == 0){
return true
}
return false
}
func is_available( x: Int, y:Int, stone: Int) -> Bool {
if ( square[x][y] != BLANK ){
return false
}
for i in 0..<8 {
let dx = DIRECTIONS_XY[i][0]
let dy = DIRECTIONS_XY[i][1]
if( self.count_reversible(x: x, y: y, dx: dx, dy: dy, stone: stone) > 0 ){
return true
}
}
return false
}
// 引数で与えられた石の次に打てる場所を返す
func available(stone: Int) -> [[Int]]{
var return_array:[[Int]] = []
for x in 0..<SIZE{
for y in 0..<SIZE{
if( self.is_available( x: x, y: y, stone: stone) ){
return_array += [[x,y]]
}
}
}
return return_array
}
// ボードに石を置く
func put( x: Int, y:Int, stone: Int){
square[x][y] = stone
for i in 0..<8 {
let dx = DIRECTIONS_XY[i][0]
let dy = DIRECTIONS_XY[i][1]
let n = self.count_reversible( x: x, y: y, dx: dx, dy: dy, stone: stone)
for j in 1..<(n+1){
square[x + j * dx][y + j * dy] = stone
}
}
}
func count_reversible( x: Int, y: Int, dx: Int, dy: Int, stone: Int) -> Int {
var _x = x
var _y = y
for i in 0..<SIZE{
_x = _x + dx
_y = _y + dy
// 0 <= x < 4 : can't write <- Annoying!!!!
if !( 0 <= _x && _x < SIZE && 0 <= _y && _y < SIZE ){
return 0
}
if (square[_x][_y] == BLANK){
return 0
}
if (square[_x][_y] == stone){
return i
}
}
return 0
}
}
player.swift
今回は簡単なランダム君だけです。
player.swift
import Foundation
class Player {
func play(board: Board, stone: Int) -> (Int,Int) {
return Random(available: board.available(stone: stone))
}
// 打てるところにランダムで打ちます。
// swift には random.choice() みたいなものありますか?
func Random(available: [[Int]]) -> (Int,Int) {
let int = Int.random(in: 0..<available.count)
return (available[int][0], available[int][1])
}
}
ViewController.swift
ViewController.swift
class ViewController: UIViewController {
// BOARDSIZE で ボードのサイズを変更できます。
let BOARDSIZE = 8
var board = Board()
var player = Player()
var player_name = "Random"
var Stone_count = 0
// -1: 黒
// 1: 白
let User_color = -1
let Cpu_color = 1
// ボードはボタンを行列配置して表現される
var buttonArray: [UIButton] = []
// board.png, white.png, black.png
let baseBoard = UIImage(named: "board")
let white = UIImage(named: "white")
let black = UIImage(named: "black")
var resetButton = UIButton()
var passButton = UIButton()
var viewStoneCount = UILabel()
// オセロ盤を表現するボタン
class buttonClass: UIButton{
let x: Int
let y: Int
init( x:Int, y:Int, frame: CGRect ) {
self.x = x
self.y = y
super.init(frame:frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("error")
}
}
// ボタンなどを生成
func createUI(){
board.start(size: BOARDSIZE)
var y = 83
let boxSize = 84 / (BOARDSIZE/4)
viewStoneCount.frame = CGRect(x: 18, y: 430, width: 330, height: 60)
viewStoneCount.textAlignment = NSTextAlignment.center
viewStoneCount.font = UIFont.systemFont(ofSize: 25)
self.view.addSubview(viewStoneCount)
for i in 0..<BOARDSIZE{
var x = 19
for j in 0..<BOARDSIZE{
let button: UIButton = buttonClass(
x: i,
y: j,
frame:CGRect(x: x,y: y, width: boxSize,height: boxSize))
button.addTarget(self, action: #selector(ViewController.pushed), for: .touchUpInside)
self.view.addSubview(button)
button.isEnabled = false
buttonArray.append(button)
x = x + boxSize + 1
}
y = y + boxSize + 1
}
resetButton.frame = CGRect(x: 125, y: 575, width: 125, height: 45)
resetButton.addTarget(self, action: #selector(ViewController.pushResetButton), for: .touchUpInside)
resetButton.isEnabled = false
resetButton.isHidden = true
resetButton.setTitle("RESET", for: .normal)
resetButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
resetButton.setTitleColor(.white, for: .normal)
resetButton.backgroundColor = UIColor(red: 0.3, green: 0.7, blue: 0.6, alpha: 1)
resetButton.layer.cornerRadius = 25
resetButton.layer.shadowOpacity = 0.5
resetButton.layer.shadowOffset = CGSize(width: 2, height: 2)
self.view.addSubview(resetButton)
passButton.frame = CGRect(x: 150, y: 500, width: 80, height: 30)
passButton.addTarget(self, action: #selector(ViewController.pushPassButton), for: .touchUpInside)
passButton.isEnabled = false
passButton.isHidden = true
passButton.setTitle("Pass", for: .normal)
passButton.titleLabel?.font = UIFont.systemFont(ofSize: 25)
passButton.setTitleColor(.white, for: .normal)
passButton.backgroundColor = UIColor(red: 0.3, green: 0.7, blue: 0.6, alpha: 1)
passButton.layer.cornerRadius = 25
passButton.layer.shadowOpacity = 0.5
passButton.layer.shadowOffset = CGSize(width: 2, height: 2)
self.view.addSubview(passButton)
drawBoard()
}
// passButton を押された時の処理
@objc func pushPassButton() {
CpuTurn()
passButton.isEnabled = false
passButton.isHidden = true
}
// resetButton を押された時の処理
@objc func pushResetButton() {
board.reset()
drawBoard()
resetButton.isEnabled = false
resetButton.isHidden = true
passButton.isEnabled = false
passButton.isHidden = true
}
// ボード盤をタッチされた時の処理
@objc func pushed(mybtn: buttonClass){
mybtn.isEnabled = false
board.put(x: mybtn.x, y: mybtn.y, stone: User_color)
drawBoard()
if( board.gameOver() == true ){
resetButton.isEnabled = true
resetButton.isHidden = false
}
self.CpuTurn()
}
// CPU
func CpuTurn() {
if( board.available(stone: Cpu_color).count != 0 ){
let xy = player.play(board: board, stone: Cpu_color)
board.put(x: xy.0, y: xy.1, stone: Cpu_color)
drawBoard()
if( board.gameOver() == true ){
resetButton.isHidden = false
resetButton.isEnabled = true
}
}
if( board.gameOver() == true ){
resetButton.isHidden = false
resetButton.isEnabled = true
}
if( board.available(stone: User_color).count == 0){
passButton.isHidden = false
passButton.isEnabled = true
}
}
// 画面にオセロ盤を表示させる
func drawBoard(){
let stonecount = board.returnStone()
viewStoneCount.text = "● Uer: " + String(stonecount.0) + " ○ CPU: " + String(stonecount.1)
var count = 0
var _board = board.return_board()
for y in 0..<BOARDSIZE{
for x in 0..<BOARDSIZE{
if( _board[y][x] == User_color ){
buttonArray[count].setImage(black, for: .normal)
} else if( _board[y][x] == Cpu_color ){
buttonArray[count].setImage(white, for: .normal)
} else {
buttonArray[count].setImage(baseBoard, for: .normal)
}
buttonArray[count].isEnabled = false
count += 1
}
}
let availableList = board.available(stone: User_color)
for i in 0..<(availableList.count){
let x = availableList[i][0]
let y = availableList[i][1]
buttonArray[x*BOARDSIZE+y].isEnabled = true
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
self.createUI()
}
}
まとめ
kotlinと似ていてとても書きやすかったです。今回はランダム君としか対戦できませんが、サーバーと通信することで強いCPUと対戦することができます。改善点や疑問点などがありましたらコメントお願いします。kotlin版もあとで書きます。