安开发一个苹果手机App:俄罗斯四方游戏

最后落得接触测试代码table_test.exs

玩耍测试

玩之关键性程序完成后,就足以一边测试一边修改

测试的上要专注测试各种边界情况,例如

用手上方移动到最左边,看看碰到边界的时节会无会见错

以手上方卡入一个刚好插入的空槽形状中,看程序是无是碰头拧

待眼前方掉入一个空槽,然后立即转,理论及相应不可知旋转了,测试看看程序会无会见出错

测试一次性消掉4实行,看看程序是无是碰头拧

倘协调测试的基本上了,就好将先后打包发送给爱人测试

突发性,开发的人口蛮麻烦测试自己编排的次第,但是反而为别人可以测试出多题材

 

切切实实代码实现

由于篇幅所限,这里不得不约讲述核心之部分代码实现方式

出准备:

率先得到注册一个AppleID

接下来于Mac电脑及设置好Xcode开发软件

引入相关工具开发包:

导入cocos2d开发工具包及要的一部分体系Frameworks

数据操作类:

AllData.h

#define SIZE 24

#define WIDTH 320

#define HEIGHT 480

@interface AllData : NSObject

@property int next;

@property int current;

@property int currentstatus;

@property int posx;

@property int posy;

@property int alllines;

@property (nonatomic,retain) NSMutableArray *numberdatas;

//取得时相近的实例

+(AllData *) sharedAllData;

//初始化棋盘

-(void)initAllData;

//取得下一个方

-(int) getNextValue;

//逆时针旋转当前方

-(void)changeLeft;

//顺时针旋转当前方

-(void)changeRight;

//判断是否可以下浮当前方

-(boolean)canMoveDown;

//处理消除慢行操作

-(void)removeLines;

@end

积存控制类:

DBUtil.h

@interface DBUtil : NSObject

+ (NSString *)dataFilePath;

+ (void)initDataBase;

//存储一店铺打消除行数数字

+(void)insertOneData:(int)topnumber;

//取得最要命的8独记录数:消除行数

+ (NSMutableArray *)getListDataFromDb;

@end

四方绘画类:

#import “CommonUtil.h”

#import “AllData.h”

@implementation CommonUtil

//绘画棋盘中的一个格子

+ (void) drawOneNumber:(int)number pos:(CGPoint)pos
layer:(CCLayer*)layer {

CCSpriteBatchNode *numbatch = [CCSpriteBatchNode
batchNodeWithFile:[NSString stringWithFormat:@”num_%d.png”,number]
capacity:15];

numbatch.anchorPoint = CGPointZero;

[numbatch setPosition:pos];

[layer addChild:numbatch];

CCSprite *sprite1 = [CCSprite spriteWithTexture:numbatch.texture
rect:CGRectMake(0, 0, SIZE-2, SIZE-2)];

sprite1.position = ccp(0,0);

sprite1.anchorPoint = CGPointZero;

[numbatch addChild:sprite1];

}

// 绘画整个棋盘

……

起先进入主界面:

@implementation AppDelegate

– (void) applicationDidFinishLaunching:(UIApplication*)application

{

……

[[AllData sharedAllData] initAllData];

[DBUtil initDataBase];

[[CCDirector sharedDirector] runWithScene: [MainLayer scene]];

…….

状态栏和工具栏:

@interface ToolLayer : CCLayer {

}

@implementation ToolLayer

– (void) drawTool {

[self removeAllChildrenWithCleanup:YES];

//画底部背景

CCSprite *bg = [CCSprite spriteWithFile:@”tool_bootom_back.png”];

bg.anchorPoint = CGPointZero;

[self addChild:bg z:0 tag:0];

//排行按钮

CCMenuItem *gold = [CCMenuItemImage itemFromNormalImage:@”gold.png”
selectedImage:@”gold_pressed.png” target:self
selector:@selector(gold:)];

//关于按钮

CCMenuItem *about = [CCMenuItemImage itemFromNormalImage:@”about.png”
selectedImage:@”about_pressed.png” target:self
selector:@selector(about:)];

//重新开按钮

CCMenuItem *restart = [CCMenuItemImage
itemFromNormalImage:@”restart.png” selectedImage:@”restart_pressed.png”
target:self selector:@selector(restart:)];

……

// 绘画消除行数的图标

// 绘画消除行数的数字

// 绘画下一个四方的图标

– (void) restart:(id) sender

{

[[AllData sharedAllData] initAllData];

MapLayer *mapLayer = (MapLayer *)[[CCDirector
sharedDirector].runningScene getChildByTag:0];

[mapLayer startGame];

[self drawTool];

}

棋盘滑动手势控制措施:

– (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {

NSSet *allTouches = [event allTouches];

switch ([allTouches count])

{

case 1:

{

UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];

single = [touch1 locationInView:[touch1 view]];

} break;

default:

break;

}

}

– (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {

NSSet *allTouches = [event allTouches];

switch ([allTouches count])

{

case 1:

{

UITouch *touch1 = [[allTouches allObjects] objectAtIndex:0];

CGPoint singleend = [touch1 locationInView:[touch1 view]];

float x = single.x – singleend.x;

float y = single.y – singleend.y;

if (x*x > y*y && x*x > 2500 && x > 0) {

//向左横向滑动超过50

NSLog(@”左”);

[self changeLeft];

}else if (x*x > y*y && x*x > 2500 && x < 0) {

//向右侧横向滑动超过50

NSLog(@”右”);

[self changeRight];

}else if (x*x < y*y && y*y > 2500 && y < 0) {

//向下就为滑动超过50

NSLog(@”下”);

[self moveDown];

}

} break;

default:

break;

}

}

主界面程序:

@interface MapLayer : CCLayer {

}

@implementation MapLayer

-(void) startGame{

[self removeAllChildrenWithCleanup:YES];

int toppos = 0;

for (NSMutableArray *onerow in [AllData sharedAllData].numberdatas) {

int leftpos = 0;

toppos = toppos + SIZE + 1;

for (NSNumber *onepos in onerow) {

leftpos = leftpos + SIZE + 1;

[CommonUtil drawOneNumber:[onepos intValue] pos:CGPointMake(leftpos,
toppos) layer:self];

}

}

// 设置定时器处理函数,定时间隔时间1秒

……

}

//定时器处理函数

//判断时方如果可以向下移动,则为下活动,修改二维列表变量

//如果无得以下浮了,则判断是否可免去某行,如果排除了某行则上之所有行的价完下换一实行

//如果未可以下浮也不可以解了,则用生一个四方的数值赋值给当下方,随机数异常成下一个四方

//然后判断时方是否足以放入棋盘最顶端的中级位置,如果得以放入则改二维列表变量,如果不能够放入,则玩结束

……

切实代码这里不贴了,有矣详细的算法程序设计和数据存储类型,要实现出来并无是极度为难,只是要耐心细心而已。

搭上章,我新建了单app做包含Table模型,
TableServer等。Table桌子的代码暂时如下, 有一部分态还尚未因此上

俄罗斯方块游戏,是风靡世界几十年的经游戏,相信大部分人数还娱乐过这个游戏,那么其是怎么样编写出来的,我们怎样才能够团结编写一个如此的嬉戏吗?

id,     allow_count,   base_gold,   need_gold,     desc
int,    int,           int,         int,        string
1,      4,             10,          480,         新手场
2,      4,             100,         4800,        中级场
3,      4,             1000,        48000,       高级场

游玩运行平台跟界面设计

假若坐无限盛的苹果手机作为游戏运行条件,则要支付一个苹果手机App

苹果手机应用的是iOS操作系统,开发环境亟待Mac电脑以及开发工具软件Xcode

开发语言可以下objective-C,开发娱乐可使出用工具包cocos2d

设急需发布到苹果AppStore,则要一个开发者账号,以及同样年99美金

界面设计:

次第启动之后上主界面,只能竖屏显示游戏

主界面分为横有的

左侧为棋盘:显示任何棋盘,正方形的格子组成,宽10束缚高20格

随苹果手机尺寸320接触*480触及来计划,20格高度为480碰,则涨幅10格占据240沾

故而左边棋盘占用240点,右侧剩余80接触

下手分为上部及脚

上部也状态栏靠上显得:最上面显示显示当前消掉的行数,下面显示方块类型小图标

下为按钮栏靠下显得:从达到于下显得3个按钮,排行榜/关于/重新开始

大概的界面设计如下示意图:

界面运行逻辑:

点击排行榜按钮上一个新页面,上面显示消掉的行数最多之8只行数数字,下面是一个回去按钮

点击关于按钮进入一个新页面,上面显示平摆设说明图片,下面是一个赶回按钮

点击重新开按钮,将手上棋盘清空并再次开始同商厦打,游戏启动

戏启动以后,第一个方块从不过上面中间开始于下丢得,每秒下落一行

又,上方的状态栏,显示下一个快要面世的方类型小图标

正方下落过程中,在屏幕及一味因左滑可以通往左逆时针旋转当前方,或者在屏幕上单因右滑得望右侧顺时针旋转当前方

尚可以屏幕及单指下滑可以给眼前方直接掉落到底层

方如果无能够更望生活动一行的当儿,则方块停止,同时开班判断是否能够消掉某些行

一经会消掉某些行,则统计消掉的行数,增加到上状态栏显示消掉的数字上,刷新显示

消掉的行从屏幕及解除,同时上的具有执行向下一体化走

加盟棋盘最上方中间的供方块出现的地方已经为方块占住了,则以为当下等同柜打结束

打闹了则弹有一个窗口展示游戏结束信息,显示总共消掉了小行,然后拿拖欠记录封存及数据库中

弹来窗口达到发生返回按钮,点击返回按钮,弹出窗口关闭,回到游戏主界面,主界面停止游戏,点击重新开按钮开始新一商行

是txt可以由excel通过xslx2csv工具转。然后我们以table_config.txt
生成代码配置table_config.ex.

玩耍发布

脚介绍一下大体的昭示流程,让大家发出只起来印象,详细的揭晓流程网上可查找到很多

打闹测试到基本没有错后,就可以以游戏发布暨苹果之AppStore了

发布App需要购置苹果的开发者账号,一年是99美金,目前为此信用卡支出也要命便利

然后登陆https://developer.apple.com,进行一些证书的设置

扭转好证明后,将证书下充斥至Mac电脑被导入到Xcode之中

下一场采用Xcode进行编译和打包好一个葡京游戏网址ipa文件

接下来登陆https://itunesconnect.apple.com创建一个App,填写一些介绍信息,需要一些App的截图

然后回来Xcode使用Application
Loader这个工具软件将ipa文件上传到itunesconnect当中

接下来回itunesconnect网站上提交App

下一场就是等苹果AppStore的甄别,一般7-10只工作日可以完成对

形成查处后,你的App就会见产出于苹果手机的AppStore里面了,就好搜索到了

接下来就足以告知您的心上人等,让他们吃惊吧。

=

defmodule TabelTest do
  use ExUnit.Case
  # import PipeHere
  setup do
      config = TableConfig.get(1)   
      table = Table.new(config)
      {:ok, table: table}
  end

  test "table is full ", %{table: table} do
      new_table =
        1..table.config.allow_count 
        |> Stream.map(&Player.new/1)
        |> Enum.reduce(table, fn p, acc -> Table.check_in(acc, p) end)
    assert new_table |> Table.is_full?
  end

  test "table has player", %{table: table} do
       p1 = Player.new(1)
       p2 = Player.new(2)
       new_table = Table.check_in(table, p1)
       assert Table.has_player?(new_table, p1.base_info.id)
       refute Table.has_player?(table, p2.base_info.id)
  end

  test "table check_in_and_out", %{table: table} do
        p1 = Player.new(1)
        new_table = Table.check_in(table, p1)
        check_out_table = Table.check_out(new_table, p1.base_info.id)
        refute Table.has_player?(check_out_table, p1.base_info.id)
  end
end

编程思路与算法设计

数据存储:

设想用二维列表来存储棋盘上之每一个格子

[[0,0,0,0,0,0,0,0,0,0],

[0,0,0,0,0,0,0,0,0,0],

……

[0,0,0,0,0,0,0,0,0,0]]

二维列表中,第一交汇是拓宽总计20实施,第二重叠内是放每一行的10单格子

如格子里面来方块,则存放数字1,如果无方块则存放数字0

判断一行要所有凡1,则表示该行填满了方格,需要消掉

行使下列数字来针对7种方块进行分,存储2只变量,当前方和下一个方

1:长条形 2:正方形 3:T型 4:L型向左 5:L型向右侧 6:S型左上右侧下 7:S型右上左下

生成下一个四方则利用随机函数生成1到7当遭遇之妄动数

然后不同方块如果展开了兜会油然而生不同之状态,定义旋转状态如下:

11:竖立长条 12:横排长条

21:正方形

31:T型尖头朝下 32:T型尖头朝左 33:T型尖头朝上 34:T型尖头朝右

41:L型短头向左长头向上 42:L型短头向达长头向右 43:L型短头向右侧长头向下
44:L型短头向下长头向左

51:L型短头向右侧长头向上 52:L型短头向下长头向右 53:L型短头向左长头向下
54:L型短头向达长头向左

61:S型左上右侧下戳 62:S型左上右侧下横排

71:S型右上错下戳 72:S型右上错误下横排

以2个变量x,y存储时方的左上角的正方的行号和列号

下变量存储时都破的行数

算法设计:

点击重新开始按钮后,主程序启动

清空整个棋盘,对二维列表全部数字清零

接下来转第1个随机数,赋值给当下方变量

下一场变第2只随机数,赋值给下一个方变量

刷新显示页面

进入定时处理函数,定时间隔时间为1秒:

定时处理函数:

认清当前方如果得以为生移动,则为下活动,修改二维列表变量

倘无可以下浮了,则判断是否可免去某行,如果排除了某行则上之所有行的价值完下更换一尽

只要未得以下浮也不可以消除了,则用生一个四方的数值赋值给当下方,随机数异常成下一个四方

接下来判断时方是否足以放入棋盘最上面的高中级位置,如果可以放入则改二维列表变量,如果无克放入,则打了

看清时方是否可以下更换函数:

基于当下方类型变量,以及方块即盘状态,结合二维列表变量判断,举例:

设是11立长条形,则冲当前方左上角的行号和列号,可以了解最下方的坐标位置

比如x=0实施,y=5排列,则太下方的坐标为(3,5),那么要看二维列表的(4,5)如果也1尽管不得下更换,否则可以下浮

依使是31:T型尖头朝下

x=0,y=4,那么下方可能出3独方块可能遇见阻碍,分别是(0,4)/(1,5)/(0,6)

这就是说只要看二维列表的(1,4)/(2,5)/(1,6)只要发生一个啊1虽然不得下转移,否则可以下浮

看清是否可祛除某行函数:

针对二维列表进行巡回,如果一行当中的有所值都为1,则可免去

一直用拥有地方的行之值为下复制,最上面一行的价值周赋值为0,同时总的消除行数变量加1

判定时方是否好放入棋盘最上方的中间位置函数:

基于当下方类型变量,以及方块即盘状态,结合二维列表变量判断,举例:

假定是1长带状,默认为11建长条形

虽用看清(0,5)/(1,5)/(2,5)/(3,5)这4单坐标在二维列表中是否也1,只要来1独岗位吗1虽说不行放入

按使是3T型尖头朝下

则要判定(0,4)/(0,5)/(0,6)/(1,5)这4单坐标

旋转处理:

一共允许有3栽滑动手指操作,向下/向左/向右侧

朝下滑动手指,表示将手上方直接掉到最好下方直到碰到阻碍停住

为左滑动手指,表示将手上方进行逆时针旋转

通往右侧滑动手指,表示将手上方进行顺时针旋转

向下丢得到处理:

又调用前面定义的论断时方是否可生移函数

起第一行循环到最后一实行坐标,即可得出最多可落到啦一行

逆时针旋转处理:

因目前方类型变量,以及方块即转状态,结合二维列表变量判断,举例:

而是11戳长条形,旋转中心点取从上往下第2独方块

这就是说根据旋转后要占用的职,需要判定(1,4)/(1,6)/(1,7)这3单职位于二维列表中之价值必须是0才可以转动

旋转完成后应左上角坐标应该由(0,5)变成(1,4),方块时转状态从11变为12

恳请圈下的示意图:

那么,顺时针旋转和这看似,只是选择后的左上角左边变量变化,以及方块即转状态值变化

咱俩得相关的部署table_config.txt

游戏规则定义

俄罗斯四方游戏有如下规则:

棋盘由涨幅为10格高度也20格的方构成

正方共发生7栽造型:

长条形1个,正方形1个,T型1个,L型2个,S型2个

方出现在极度上端正中间,同时知道下一个四方是呀

四方往生掉得,每隔1秒下落一行

正方下落中如果赶上有方块阻挡不能够退则止下滑

四方停止下落后,判断是否来整行都给方块填满,如果有,则整行消失,上面的周尽完全下降一行

统计消掉的总店多次

方于下降过程遭到,可以随往下方向键让方直接掉到直到停止职务

得遵循往左或为右侧向键让方进行盘,每本键一破盘90过

一旦无足够的长空为方落下虽玩结束

defmodule Table do

    @state_accept 0   #准备接入玩家
    @state_ready 1    #开局准备?
    defdelegate [fetch(t, key), get_and_update(t, key, list)], to: Map
    defstruct [:config, :seats, :state]
    def new(config) do
        %Table{
            config: config,
            seats: %{},
            state: @state_accept
        }
    end

    def is_full?(table) do
        cur_count = Enum.count(table.seats)
        cur_count >= table.config.allow_count
    end

    def has_player?(table, player_id) do
        table.seats[player_id]
    end

    def check_in(table, player) do
        update_in(table.seats, &(Map.put(&1, player.base_info.id, player)))
    end

    def check_out(table, player_id) do
        update_in(table.seats, &(Map.delete(&1, player_id)))
    end
end

打开发步骤

如果编写一个玩,基本以以下部分手续进行:

1.游戏规则定义

游戏规则决定了是戏怎么打,好不好玩,也不怕决定了编写程序实现的目标。不同之急需导致差之条条框框,不同的规则就是得不同之主意去贯彻。

比如说,俄罗斯方块是由于哪些类型方块构成,棋盘布局是9*18还是10*20,是否同意旋转,消掉一行获得的分数和得多实践获得的分如何开展添加,等等。

2.摘游戏运行平台及界面设计

于充分多种阳台及且足以玩游戏,常见的本电脑pc或者笔记本上面直接运行游戏exe程序,或者在微机浏览器中运作flash游戏,或者手机上运行游戏App,或者当异常游戏机比如xbox等达成一直运行游戏程序,或者在VR设备及运行游戏程序。

不同的平台,需要不同的贯彻方式,也尽管需要为此不同之次要开发语言开发工具来贯彻。

譬如,在网页上付出娱乐,可以就此html+css+js实现,也得用flash实现;在手机上出娱乐,可以用java语言在android系统受到实现,也可以为此objective-c在ios系统及贯彻;在处理器pc机上得用c语言在windows系统中落实。

界面设计就是冲游戏规则决定人机交互界面,决定了输入和出口的法门。

像,俄罗斯方块可以计划改为左边是棋盘,右侧上面是分,下一个方,还可以推广一个重来平等句子按钮,或者暂停按钮等等。

正方设计改为纯一栽颜色块,还是多颜色块,或者是正在的还是圆满的,或者是卡通图案,这些虽是界面设计。

3.设想编程思路与算法设计

一旦控制了以哪种平台与因此何种开发语言来编程实现休闲游,则只要考虑编程的思路,数据的存储,以及现实的算法。

例如,俄罗斯方根据棋盘大小是故二维数组来落实,还是用平等维数组来促成,还是用字典表来实现

同等商厦打是定时计算,还是用一个循环来处理

什么保存下一个项目,如何算分数,如何判断一个方是否生不克重走,如何判定一行是否已为填满,如何判定游戏早已竣工

4.照前面设计的思绪编写代码实现

设想好编程思路和算法设计下,可以按照设想进行实际的编程开发

非停止的证实思路,通过开发中之莫过于状况,可能针对编程思路进行修改调整,直到好全套娱乐效果

代码就是单排一行的编纂出来,直到成为一个足以运行的一日游程序

5.游戏测试

测试就是管自己当成实际用户来运转游戏程序,找到游戏或者存在的题目,避免程序尚未按照预期执行,防止程序崩溃死机等情事,验证游戏确实仍游戏规则能完全可靠的运转,这个开发人员可以搜寻其他人进行测试,防止投机存思维盲区。

6.游戏发布

戏测试完了以后,就得用游乐发布暨对应的水道或平台,让还多之人足玩玩至是游戏。

像,苹果手机App可以宣布到苹果的AppStore,安卓手机App可以发布暨各国大利用市场。

最好简便的宣布即是一直发送给好的意中人,让他俩大吃一惊。

defmodule TableConfig do
    Module.register_attribute __MODULE__, :column_names, []
    Module.register_attribute __MODULE__, :column_types, []
    Module.register_attribute __MODULE__, :config, []
    line_with_index = File.stream!(Path.join([__DIR__, "table_config.txt"]) , [], :line) 
                      |> Stream.with_index
    for {line, index} <- line_with_index do
        items = line |> String.split(",") |> Stream.map(&String.strip(&1)) 
        case index do
            0 ->
                @column_names items |> Enum.map(&String.to_atom(&1))
            1 ->
                @column_types items 
                              |> Stream.with_index 
                              |> Enum.map(fn {v, i} -> {i, String.to_atom(v)} end)
                              |> IO.inspect
                              |> Enum.into(%{})
            _ ->
                new_items = items 
                            |> Stream.with_index
                            |> Stream.map( &( TypeConverter.convert(&1, @column_types) ) )    
                zip = Enum.zip(@column_names, new_items)
                @config  Enum.into(zip, %{})
                IO.inspect @config
            # 以下函数花了我点时间,最后不得不通过模块属性完成,我不知道有没有其他方法
         # 早期的版本是者这样的
               # config = Enum.into(zip, %{})
               # def get(unquote(config.id)) do
               #   unquote(config)  # 这里会报错,百思不得其解,在ErrorMsg里我是这样用的,没有问题。不知2者区别在哪
               # end

                def get(unquote(@config.id)) do
                    @config     
                end
        end
    end

end

下同样有点节会从牌局开始吧,然后TableServer,然后让其跑起。

咱自然好以TableConfig里经由excel文件直接扭转,那样会重复便利。