【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson2: 僵尸攻擊人類


CryptoZombies是個(gè)在編游戲的過程中學(xué)習(xí)Solidity智能合約語言的互動(dòng)教程澜倦。本教程是為了Solidity初學(xué)者而設(shè)計(jì)的,會(huì)從最基礎(chǔ)開始教起,即便你從來沒有接觸過Solidity也可以學(xué),CryptoZombies會(huì)手把手地教你秤涩。


今天我們來學(xué)習(xí)第2課僵尸攻擊人類,在本課里將學(xué)會(huì)如何通過獵食其他生物捡多,擴(kuò)張你的僵尸軍團(tuán)蓖康。本課會(huì)使用到一些高級的 Solidity 概念,所以最好完成第一課的內(nèi)容垒手。

進(jìn)入第2課的第1章蒜焊,右側(cè)欄有個(gè)游戲,選擇上面的僵尸科贬,再選擇下面三個(gè)人的一個(gè)泳梆,會(huì)產(chǎn)生一個(gè)新的新僵尸種類。本課就是此游戲的實(shí)現(xiàn)榜掌。

2.1 映射(Mapping)和地址(Address)

我們通過給數(shù)據(jù)庫中的僵尸指定“主人”优妙, 來支持“多玩家”模式。如此一來憎账,我們需要引入2個(gè)新的數(shù)據(jù)類型:mapping(映射) 和 address(地址)套硼。

Addresses (地址)

以太坊區(qū)塊鏈由 account (賬戶)組成,你可以把它想象成銀行賬戶胞皱。一個(gè)帳戶的余額是以太 (在以太坊區(qū)塊鏈上使用的幣種)邪意,你可以和其他帳戶之間支付和接受以太幣,就像你的銀行帳戶可以電匯資金到其他銀行帳戶一樣反砌。每個(gè)帳戶都有一個(gè)“地址”雾鬼,你可以把它想象成銀行賬號。這是賬戶唯一的標(biāo)識符宴树,它看起來長這樣:

0x0cE446255506E92DF41614C46F1d6df9Cc969183
(這是 CryptoZombies 團(tuán)隊(duì)的地址策菜,如果你喜歡 CryptoZombies 的話,請打賞一些以太幣森渐!??)

我們將在后面的課程中介紹地址的細(xì)節(jié)做入,現(xiàn)在你只需要了解地址屬于特定用戶(或智能合約)的。所以我們可以指定“地址”作為僵尸主人的 ID同衣。當(dāng)用戶通過與我們的應(yīng)用程序交互來創(chuàng)建新的僵尸時(shí)竟块,新僵尸的所有權(quán)被設(shè)置到調(diào)用者的以太坊地址下。

Mapping(映射)

在第1課中耐齐,我們看到了結(jié)構(gòu)體和數(shù)組 浪秘。 映射是另一種在 Solidity 中存儲(chǔ)有組織數(shù)據(jù)的方法蒋情。映射是這樣定義的:

//對于金融應(yīng)用程序,將用戶的余額保存在一個(gè) uint類型的變量中:
mapping (address => uint) public accountBalance;
//或者可以用來通過userId 存儲(chǔ)/查找的用戶名
mapping (uint => string) userIdToName;

映射本質(zhì)上是存儲(chǔ)和查找數(shù)據(jù)所用的鍵值對耸携。在第一個(gè)例子中棵癣,鍵是一個(gè) address,值是一個(gè) uint夺衍,在第二個(gè)例子中狈谊,鍵是一個(gè)uint,值是一個(gè) string沟沙。

2.2 Msg.sender

現(xiàn)在有了一套映射來記錄僵尸的所有權(quán)了河劝,我們可以修改_createZombie 方法來運(yùn)用它們。為了做到這一點(diǎn)矛紫,我們要用到msg.sender赎瞎。

msg.sender

在Solidity中,有一些全局變量可以被所有函數(shù)調(diào)用颊咬。 其中一個(gè)就是 msg.sender务甥,它指的是當(dāng)前調(diào)用者(或智能合約)的address。

注意:在Solidity中喳篇,功能執(zhí)行始終需要從外部調(diào)用者開始敞临。 一個(gè)合約只會(huì)在區(qū)塊鏈上什么也不做,除非有人調(diào)用其中的函數(shù)杭隙。所以msg.sender總是存在的哟绊。以下是使用 msg.sender 來更新 mapping 的例子:

mapping (address => uint) favoriteNumber;

function setMyNumber(uint _myNumber) public {
  // 更新我們的 `favoriteNumber` 映射來將 `_myNumber`存儲(chǔ)在 `msg.sender`名下
  favoriteNumber[msg.sender] = _myNumber;
  // 存儲(chǔ)數(shù)據(jù)至映射的方法和將數(shù)據(jù)存儲(chǔ)在數(shù)組相似
}

function whatIsMyNumber() public view returns (uint) {
  // 拿到存儲(chǔ)在調(diào)用者地址名下的值
  // 若調(diào)用者還沒調(diào)用 setMyNumber, 則值為 `0`
  return favoriteNumber[msg.sender];
}

在這個(gè)小小的例子中痰憎,任何人都可以調(diào)用setMyNumber在我們的合約中存下一個(gè)uint并且與他們的地址相綁定票髓。 然后,他們調(diào)用whatIsMyNumber 就會(huì)返回他們存儲(chǔ)的uint铣耘。

使用msg.sender很安全洽沟,因?yàn)樗哂幸蕴粎^(qū)塊鏈的安全保障——除非竊取與以太坊地址相關(guān)聯(lián)的私鑰,否則是沒有辦法修改其他人的數(shù)據(jù)的蜗细。

2.3 Require

在第一課中裆操,我們成功讓用戶通過調(diào)用createRandomZombie函數(shù)并輸入一個(gè)名字來創(chuàng)建新的僵尸。 但是炉媒,如果用戶能持續(xù)調(diào)用這個(gè)函數(shù)來創(chuàng)建出無限多個(gè)僵尸加入他們的軍團(tuán)踪区,這游戲就太沒意思了!

于是吊骤,我們作出限定:每個(gè)玩家只能調(diào)用一次這個(gè)函數(shù)缎岗。 這樣一來,新玩家可以在剛開始玩游戲時(shí)通過調(diào)用它白粉,為其軍團(tuán)創(chuàng)建初始僵尸传泊。我們怎樣才能限定每個(gè)玩家只調(diào)用一次這個(gè)函數(shù)呢鼠渺?答案是使用require。require使得函數(shù)在執(zhí)行過程中眷细,當(dāng)不滿足某些條件時(shí)拋出錯(cuò)誤拦盹,并停止執(zhí)行:

function sayHiToVitalik(string _name) public returns (string) {
  // 比較 _name 是否等于 "Vitalik". 如果不成立,拋出異常并終止程序
  // (敲黑板: Solidity 并不支持原生的字符串比較, 我們只能通過比較
  // 兩字符串的 keccak256 哈希值來進(jìn)行判斷)
  require(keccak256(_name) == keccak256("Vitalik"));
  // 如果返回 true, 運(yùn)行如下語句
  return "Hi!";
}

如果你這樣調(diào)用函數(shù) sayHiToVitalik(“Vitalik”)溪椎,它會(huì)返回“Hi普舆!”。而如果調(diào)用的時(shí)候使用了其他參數(shù)校读,它則會(huì)拋出錯(cuò)誤并停止執(zhí)行奔害。因此,在調(diào)用一個(gè)函數(shù)之前地熄,用require驗(yàn)證前置條件是非常有必要的。

2.4 繼承(Inheritance)

我們的游戲代碼越來越長芯杀。 當(dāng)代碼過于冗長的時(shí)候端考,最好將代碼和邏輯分拆到多個(gè)不同的合約中,以便于管理揭厚。有個(gè)讓Solidity的代碼易于管理的功能却特,就是合約inheritance (繼承):

contract Doge {
  function catchphrase() public returns (string) {
    return "So Wow CryptoDoge";
  }
}

contract BabyDoge is Doge {
  function anotherCatchphrase() public returns (string) {
    return "Such Moon BabyDoge";
  }
}

由于BabyDoge是從Doge那里inherits(繼承)過來的。 這意味著當(dāng)你編譯和部署了BabyDoge筛圆,它將可以訪問 catchphrase() 和 anotherCatchphrase()和其他我們在Doge中定義的其他公共函數(shù)裂明。這可以用于邏輯繼承(比如表達(dá)子類的時(shí)候,Cat 是一種 Animal)太援。 但也可以簡單地將類似的邏輯組合到不同的合約中以組織代碼闽晦。

2.5 引入(Import)

在Solidity中,當(dāng)你有多個(gè)文件并且想把一個(gè)文件導(dǎo)入另一個(gè)文件時(shí)提岔,可以使用 import 語句:

import "./someothercontract.sol";

contract newContract is SomeOtherContract {

}

這樣當(dāng)我們在合約(contract)目錄下有一個(gè)名為someothercontract.sol 的文件( ./ 就是同一目錄的意思)仙蛉,它就會(huì)被編譯器導(dǎo)入。

2.6 Storage與Memory

在Solidity中碱蒙,有兩個(gè)地方可以存儲(chǔ)變量——storage或memory荠瘪。Storage 變量是指永久存儲(chǔ)在區(qū)塊鏈中的變量。Memory變量則是臨時(shí)的赛惩,當(dāng)外部函數(shù)對某合約調(diào)用完成時(shí)哀墓,內(nèi)存型變量即被移除。你可以把它想象成存儲(chǔ)在你電腦的硬盤或是RAM中數(shù)據(jù)的關(guān)系喷兼。

大多數(shù)時(shí)候你都用不到這些關(guān)鍵字篮绰,默認(rèn)情況下Solidity會(huì)自動(dòng)處理它們。 狀態(tài)變量(在函數(shù)之外聲明的變量)默認(rèn)為“存儲(chǔ)”形式褒搔,并永久寫入?yún)^(qū)塊鏈阶牍;而在函數(shù)內(nèi)部聲明的變量是“內(nèi)存”型的喷面,它們函數(shù)調(diào)用結(jié)束后消失。

然而也有一些情況下走孽,你需要手動(dòng)聲明存儲(chǔ)類型惧辈,主要用于處理函數(shù)內(nèi)的 結(jié)構(gòu)體和數(shù)組時(shí):

contract SandwichFactory {
  struct Sandwich {
    string name;
    string status;
  }

  Sandwich[] sandwiches;

  function eatSandwich(uint _index) public {
    // Sandwich mySandwich = sandwiches[_index];

    // ^ 看上去很直接,不過 Solidity 將會(huì)給出警告
    // 告訴你應(yīng)該明確在這里定義 `storage` 或者 `memory`磕瓷。

    // 所以你應(yīng)該明確定義 `storage`:
    Sandwich storage mySandwich = sandwiches[_index];
    // ...這樣 `mySandwich` 是指向 `sandwiches[_index]`的指針
    // 在存儲(chǔ)里盒齿,另外...
    mySandwich.status = "Eaten!";
    // ...這將永久把 `sandwiches[_index]` 變?yōu)閰^(qū)塊鏈上的存儲(chǔ)

    // 如果你只想要一個(gè)副本,可以使用`memory`:
    Sandwich memory anotherSandwich = sandwiches[_index + 1];
    // ...這樣 `anotherSandwich` 就僅僅是一個(gè)內(nèi)存里的副本了
    // 另外
    anotherSandwich.status = "Eaten!";
    // ...將僅僅修改臨時(shí)變量困食,對 `sandwiches[_index + 1]` 沒有任何影響
    // 不過你可以這樣做:
    sandwiches[_index + 1] = anotherSandwich;
    // ...如果你想把副本的改動(dòng)保存回區(qū)塊鏈存儲(chǔ)
  }
}

如果你還沒有完全理解究竟應(yīng)該使用哪一個(gè)边翁,也不用擔(dān)心 —— 在本教程中,我們將告訴你何時(shí)使用storage或是memory硕盹,并且當(dāng)你不得不使用到這些關(guān)鍵字的時(shí)候符匾,Solidity編譯器也發(fā)警示提醒你的。現(xiàn)在瘩例,只要知道在某些場合下也需要你顯式地聲明storage或memory就夠了啊胶!

2.7 僵尸的DNA

獲取新的僵尸DNA的公式很簡單:計(jì)算獵食僵尸DNA和被獵僵尸DNA之間的平均值。例如:

function testDnaSplicing() public {
  uint zombieDna = 2222222222222222;
  uint targetDna = 4444444444444444;
  uint newZombieDna = (zombieDna + targetDna) / 2;
  // newZombieDna 將等于 3333333333333333
}

以后垛贤,我們也可以讓函數(shù)變得更復(fù)雜些焰坪,比方給新的僵尸的DNA增加一些隨機(jī)性之類的。但現(xiàn)在先從最簡單的開始——以后還可以回來完善它嘛聘惦。

2.8 關(guān)于函數(shù)可見性

我們上一課的代碼有問題某饰!編譯的時(shí)候編譯器就會(huì)報(bào)錯(cuò)。錯(cuò)誤在于善绎,我們嘗試從ZombieFeeding中調(diào)用_createZombie函數(shù)黔漂,但_createZombie卻是ZombieFactory的private(私有)函數(shù)。這意味著任何繼承自 ZombieFactory的子合約都不能訪問它禀酱。

internal 和 external

除public和private屬性之外瘟仿,Solidity還使用了另外兩個(gè)描述函數(shù)可見性的修飾詞:internal(內(nèi)部) 和 external(外部)。

internal和private 類似比勉,不過劳较, 如果某個(gè)合約繼承自其父合約,這個(gè)合約即可以訪問父合約中定義的“內(nèi)部”函數(shù)浩聋。(嘿观蜗,這聽起來正是我們想要的那樣!)衣洁。

external 與public 類似墓捻,只不過這些函數(shù)只能在合約之外調(diào)用——它們不能被合約內(nèi)的其他函數(shù)調(diào)用。稍后我們將討論什么時(shí)候使用external和 public。聲明函數(shù)internal或external類型的語法砖第,與聲明private和public類 型相同:

contract Sandwich {
  uint private sandwichesEaten = 0;

  function eat() internal {
    sandwichesEaten++;
  }
}

contract BLT is Sandwich {
  uint private baconSandwichesEaten = 0;

  function eatWithBacon() public returns (string) {
    baconSandwichesEaten++;
    // 因?yàn)閑at() 是internal 的撤卢,所以我們能在這里調(diào)用
    eat();
  }
}

2.9 僵尸吃什么?

是時(shí)候讓我們的僵尸去捕獵! 那僵尸最喜歡的食物是什么呢梧兼?Crypto僵尸喜歡吃的是...CryptoKitties放吩! ??????(正經(jīng)點(diǎn),我可不是開玩笑??)

為了做到這一點(diǎn)羽杰,我們要讀出CryptoKitties智能合約中的kittyDna渡紫。這些數(shù)據(jù)是公開存儲(chǔ)在區(qū)塊鏈上的。區(qū)塊鏈?zhǔn)遣皇呛芸峥既縿e擔(dān)心 —— 我們的游戲并不會(huì)傷害到任何真正的CryptoKitty惕澎。 我們只讀取CryptoKitties 數(shù)據(jù),但卻無法在物理上刪除它颜骤。

與其他合約的交互

如果我們的合約需要和區(qū)塊鏈上的其他的合約會(huì)話唧喉,則需先定義一個(gè) interface (接口)。

先舉一個(gè)簡單的栗子忍抽。 假設(shè)在區(qū)塊鏈上有這么一個(gè)合約:

contract LuckyNumber {
  mapping(address => uint) numbers;

  function setNum(uint _num) public {
    numbers[msg.sender] = _num;
  }

  function getNum(address _myAddress) public view returns (uint) {
    return numbers[_myAddress];
  }
}

這是個(gè)很簡單的合約欣喧,您可以用它存儲(chǔ)自己的幸運(yùn)號碼,并將其與您的以太坊地址關(guān)聯(lián)梯找。 這樣其他人就可以通過您的地址查找您的幸運(yùn)號碼了。現(xiàn)在假設(shè)我們有一個(gè)外部合約益涧,使用getNum函數(shù)可讀取其中的數(shù)據(jù)锈锤。首先,我們定義LuckyNumber合約的interface :

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

請注意闲询,這個(gè)過程雖然看起來像在定義一個(gè)合約久免,但其實(shí)內(nèi)里不同:首先,我們只聲明了要與之交互的函數(shù)——在本例中為getNum——在其中我們沒有使用到任何其他的函數(shù)或狀態(tài)變量扭弧。其次阎姥,我們并沒有使用大括號({ 和 })定義函數(shù)體,我們單單用分號(;)結(jié)束了函數(shù)聲明鸽捻。這使它看起來像一個(gè)合約框架呼巴。

編譯器就是靠這些特征認(rèn)出它是一個(gè)接口的。在我們的app代碼中使用這個(gè)接口御蒲,合約就知道其他合約的函數(shù)是怎樣的衣赶,應(yīng)該如何調(diào)用,以及可期待什么類型的返回值厚满。

在下一課中府瞄,我們將真正調(diào)用其他合約的函數(shù)。目前我們只要聲明一個(gè)接口碘箍,用于調(diào)用CryptoKitties合約就行了遵馆。

2.10 使用接口

繼續(xù)前面 NumberInterface 的例子鲸郊,我們既然將接口定義為:

contract NumberInterface {
  function getNum(address _myAddress) public view returns (uint);
}

我們可以在合約中這樣使用:

contract MyContract {
  address NumberInterfaceAddress = 0xab38...;
  // ^ 這是FavoriteNumber合約在以太坊上的地址
  NumberInterface numberContract = 
  NumberInterface(NumberInterfaceAddress);
  // 現(xiàn)在變量 `numberContract` 指向另一個(gè)合約對象

  function someFunction() public {
    // 現(xiàn)在我們可以調(diào)用在那個(gè)合約中聲明的 `getNum`函數(shù):
    uint num = numberContract.getNum(msg.sender);
    // ...在這兒使用 `num`變量做些什么
  }
}

通過這種方式,只要將您合約的可見性設(shè)置為public(公共)或external(外部)货邓,它們就可以與以太坊區(qū)塊鏈上的任何其他合約進(jìn)行交互秆撮。

2.11 處理多返回值

getKitty是我們所看到的第一個(gè)返回多個(gè)值的函數(shù)。我們來看看是如何處理的:

function multipleReturns() internal returns(uint a, uint b, uint c) {
  return (1, 2, 3);
}

function processMultipleReturns() external {
  uint a;
  uint b;
  uint c;
  // 這樣來做批量賦值:
  (a, b, c) = multipleReturns();
}

// 或者如果我們只想返回其中一個(gè)變量:
function getLastReturnValue() external {
  uint c;
  // 可以對其他字段留空:
  (,,c) = multipleReturns();
}

2.12 獎(jiǎng)勵(lì): Kitty 基因

我們的功能邏輯主體已經(jīng)完成了...現(xiàn)在讓我們來添一個(gè)獎(jiǎng)勵(lì)功能吧逻恐。這樣吧像吻,給從小貓制造出的僵尸添加些特征,以顯示他們是貓僵尸复隆。要做到這一點(diǎn)拨匆,咱們在新僵尸的DNA中添加一些特殊的小貓代碼。

第一課中我們提到挽拂,我們目前只使用16位DNA的前12位數(shù)來指定僵尸的外觀惭每。所以現(xiàn)在我們可以使用最后2個(gè)數(shù)字來處理“特殊”的特征。這樣吧亏栈,把貓僵尸DNA的最后兩個(gè)數(shù)字設(shè)定為99(因?yàn)樨堄?條命)台腥。所以在我們這么來寫代碼:如果這個(gè)僵尸是一只貓變來的,就將它DNA的最后兩位數(shù)字設(shè)置為99绒北。

if 語句

if語句的語法在 Solidity 中黎侈,與在 JavaScript 中差不多:

function eatBLT(string sandwich) public {
  // 看清楚了,當(dāng)我們比較字符串的時(shí)候闷游,需要比較他們的 keccak256 哈希碼
  if (keccak256(sandwich) == keccak256("BLT")) {
    eat();
  }
}

2.13 放在一起

至此峻汉,第二課已經(jīng)學(xué)完了!查看下右側(cè)的演示脐往,看看他們怎么運(yùn)行起來得吧休吠。選擇僵尸和其中一只小貓,你將斬獲一個(gè)新的小貓僵尸业簿。

JavaScript 實(shí)現(xiàn)

我們只用編譯和部署ZombieFeeding瘤礁,就可以將這個(gè)合約部署到以太坊了。我們最終完成的這個(gè)合約繼承自ZombieFactory梅尤,因此它可以訪問自己和父輩合約中的所有 public 方法柜思。我們來看一個(gè)與我們的剛部署的合約進(jìn)行交互的例子, 這個(gè)例子使用了JavaScript和web3.js:

var abi = /* abi generated by the compiler */
var ZombieFeedingContract = web3.eth.contract(abi)
var contractAddress = /* our contract address on Ethereum after deploying */
var ZombieFeeding = ZombieFeedingContract.at(contractAddress)

// 假設(shè)我們有我們的僵尸ID和要攻擊的貓咪ID
let zombieId = 1;
let kittyId = 1;

// 要拿到貓咪的DNA巷燥,我們需要調(diào)用它的API酝蜒。這些數(shù)據(jù)保存在它們的服務(wù)器上而不是區(qū)塊鏈上。
// 如果一切都在區(qū)塊鏈上矾湃,我們就不用擔(dān)心它們的服務(wù)器掛了亡脑,或者它們修改了API,
// 或者因?yàn)椴幌矚g我們的僵尸游戲而封殺了我們
let apiUrl = "https://api.cryptokitties.co/kitties/" + kittyId
$.get(apiUrl, function(data) {
  let imgUrl = data.image_url
  // 一些顯示圖片的代碼
})

// 當(dāng)用戶點(diǎn)擊一只貓咪的時(shí)候:
$(".kittyImage").click(function(e) {
  // 調(diào)用我們合約的 `feedOnKitty` 函數(shù)
  ZombieFeeding.feedOnKitty(zombieId, kittyId)
})

// 偵聽來自我們合約的新僵尸事件好來處理
ZombieFactory.NewZombie(function(error, result) {
  if (error) return
  // 這個(gè)函數(shù)用來顯示僵尸:
  generateZombie(result.zombieId, result.name, result.dna)
})

系列文章:
【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson1: 搭建僵尸工廠
【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson2: 僵尸攻擊人類
【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson3: 搭建僵尸工廠
【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson4: 僵尸作戰(zhàn)系統(tǒng)
【CryptoZombies|編寫區(qū)塊鏈游戲?qū)W智能合約】Lesson5: ERC721標(biāo)準(zhǔn)和加密收藏品

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市霉咨,隨后出現(xiàn)的幾起案子蛙紫,更是在濱河造成了極大的恐慌,老刑警劉巖途戒,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件坑傅,死亡現(xiàn)場離奇詭異,居然都是意外死亡喷斋,警方通過查閱死者的電腦和手機(jī)唁毒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來星爪,“玉大人浆西,你說我怎么就攤上這事⊥缣冢” “怎么了近零?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抄肖。 經(jīng)常有香客問我久信,道長,這世上最難降的妖魔是什么漓摩? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任裙士,我火速辦了婚禮,結(jié)果婚禮上管毙,老公的妹妹穿的比我還像新娘腿椎。我一直安慰自己,他們只是感情好锅风,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著鞍泉,像睡著了一般皱埠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上咖驮,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天边器,我揣著相機(jī)與錄音,去河邊找鬼托修。 笑死忘巧,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的睦刃。 我是一名探鬼主播砚嘴,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了际长?” 一聲冷哼從身側(cè)響起耸采,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎工育,沒想到半個(gè)月后虾宇,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡如绸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年嘱朽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怔接。...
    茶點(diǎn)故事閱讀 39,841評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡搪泳,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出蜕提,到底是詐尸還是另有隱情森书,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布谎势,位于F島的核電站凛膏,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏脏榆。R本人自食惡果不足惜猖毫,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望须喂。 院中可真熱鬧吁断,春花似錦、人聲如沸坞生。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽是己。三九已至又兵,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間卒废,已是汗流浹背沛厨。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留摔认,地道東北人逆皮。 一個(gè)月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像参袱,于是被迫代替她去往敵國和親电谣。 傳聞我的和親對象是個(gè)殘疾皇子秽梅,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,781評論 2 354