본문 바로가기
Blockchain/Testnet

AO 테스트넷 가이드 4편

by GrayChoi 2024. 3. 5.
반응형

AO Testnet Guide 4편

Bots and Games

 

이전글 : AO 테스트넷 가이드 3편

 

 

 


 

 

1. Let's Play A Game

AO-EFFECT 게임이란

실시간으로 전 세계의 친구들이나 다른 플레이어와 경쟁 할 수 있는 게임이다.

 

규칙

- 각 플레이어의 체력은 100으로 시작하며 에너지는 0으로 시작한다.

- 40x40의 맵에서 시작한다.

- 에너지는 시간이 지남에 따라 100까지 보충된다.

- 맵을 탐색하고, 다른 플레이어를 찾아 공격해서 이겨야한다.

- 범위 내에 있을 때만 에너지를 사용하여 공격할 수 있다.

- 한 명의 플레이어만 남거나 할당된 시간이 만료되면 게임이 끝이난다.

 

2. 게임 Process ID 설정

// 게임 ID의 주소도 기존 9pe-GCPGdQ-WlCKBj1E7079s0Sl75DDOVgZwhA2RJYY에서 변경되었다.
aos> Game = "3HSmhQ-lHaCQlOKtq5GDgbVQXQ6mWIp40uUASAG13Xk"

 

3. 게임 토큰 요청 및 게임 서버 등록

// 게임 서버 등록
aos> Send({ Target = Game, Action = "Register" })

// 토큰 요청
aos> Send({ Target = Game, Action = "RequestTokens" })

 

 

게임에 참가하려면 토큰이 필요한데 위 명령어를 통해 토큰을 요청할 수 있다.

 

4. 게임참가

aos> Send({ Target = Game, Action = "Transfer", Recipient = Game, Quantity = "1000" })

토큰을 받은후 입장료를 지불하여 게임에 참가한다.

몇 초만 기다리면 플레이어 결제 상태에 대한 실시간 업데이트가 화면에 표시된다.

2명이상의 플레이어가 참가한 후, 2분 후에 게임이 시작된다.

 

5. 게임 플레이

// 예시
aos> Send({ Target = Game, Action = "PlayerMove", Player = ao.id, Direction = "DownRight"})

// 방향종류, 위의 예시에서 Direction = "DownRight" 부분을 아래 원하는 종류로 변경하면된다.
Up = {x = 0, y = -1},
Down = {x = 0, y = 1},
Left = {x = -1, y = 0},
Right = {x = 1, y = 0},
UpRight = {x = 1, y = -1},
UpLeft = {x = -1, y = -1},
DownRight = {x = 1, y = 1},
DownLeft = {x = -1, y = 1}

맵의 밖으로 이동하면 그 반대편으로 이동한다.

 

6. 공격

aos> Send({ Target = Game, Action = "PlayerAttack", Player = ao.id, AttackEnergy = "energy_integer"})

3x3 범위 내의 다른 플레이어를 공격하는데 축적된 에너지를 사용한다.

 

여기서부터 대충 한번 보고 바로 11번으로 넘어가도된다.

7. Announcement 전달 봇 만들기

// Ctrl + c 를 눌러 aos 밖으로 나온다
nano bot.lua

// 생성 후 아래 코드 복사 붙여넣기
Handlers.add(
  "PrintAnnouncements",
  Handlers.utils.hasMatchingTag("Action", "Announcement"),
  function (msg)
    print(msg.Event .. ": " .. msg.Data)
  end
)

Ctrl x, Y, 엔터

핸들러의 이름은 "PrintAnnouncements"

내장된 특수 유틸리티로 hasMatchingTags를 사용하며

announcement의 메세지를 받을 때

변환하여 보여준다.

 

8. 리로드 및 테스트

.load bot.lua

 

9. 게임의 진행상태를 체크 봇 만들기

// Ctrl + c 입력 후 aos 밖으로 나온다.
nano bot.lua

// HandleAnnouncements 핸들러 코드를 아래의 코드로 대체한다.
Handlers.add(
  "HandleAnnouncements",
  Handlers.utils.hasMatchingTag("Action", "Announcement"),
  function (msg)
    ao.send({Target = Game, Action = "GetGameState"})
    print(msg.Event .. ": " .. msg.Data)
  end
)

Ctrl x, Y, 엔터

 

aos

.load bot.lua

 

10. 전체적으로 수정하기

nano bot.lua

//아래 코드로 전부 대체한다.
LatestGameState = LatestGameState or nil

Handlers.add(
"HandleAnnouncements",
Handlers.utils.hasMatchingTag("Action", "Announcement"),
function (msg)
  ao.send({Target = Game, Action = "GetGameState"})
  print(msg.Event .. ": " .. msg.Data)
end
)

Handlers.add(
"UpdateGameState",
Handlers.utils.hasMatchingTag("Action", "GameState"),
function (msg)
  local json = require("json")
  LatestGameState = json.decode(msg.Data)
  ao.send({Target = ao.id, Action = "UpdatedGameState"})
  print("Game state updated. Print \'LatestGameState\' for detailed view.")
end
)

Ctrl x, Y, 엔터

 

aos

.load bot.lua

 

11. Strategic Decisions and 자동 응답

// Ctrl + c로 aos 밖으로 나온 후
nano bot.lua

//아래 코드 전체로 대체
LatestGameState = LatestGameState or nil
InAction = InAction or false

Logs = Logs or {}

colors = {
  red = "\27[31m",
  green = "\27[32m",
  blue = "\27[34m",
  reset = "\27[0m",
  gray = "\27[90m"
}

function addLog(msg, text)
  Logs[msg] = Logs[msg] or {}
  table.insert(Logs[msg], text)
end

function inRange(x1, y1, x2, y2, range)
    return math.abs(x1 - x2) <= range and math.abs(y1 - y2) <= range
end

function decideNextAction()
  local player = LatestGameState.Players[ao.id]
  local targetInRange = false

  for target, state in pairs(LatestGameState.Players) do
      if target ~= ao.id and inRange(player.x, player.y, state.x, state.y, 1) then
          targetInRange = true
          break
      end
  end

  if player.energy > 5 and targetInRange then
    print(colors.red .. "Player in range. Attacking." .. colors.reset)
    ao.send({Target = Game, Action = "PlayerAttack", Player = ao.id, AttackEnergy = tostring(player.energy)})
  else
    print(colors.red .. "No player in range or insufficient energy. Moving randomly." .. colors.reset)
    local directionMap = {"Up", "Down", "Left", "Right", "UpRight", "UpLeft", "DownRight", "DownLeft"}
    local randomIndex = math.random(#directionMap)
    ao.send({Target = Game, Action = "PlayerMove", Player = ao.id, Direction = directionMap[randomIndex]})
  end
  InAction = false
end

Handlers.add(
  "PrintAnnouncements",
  Handlers.utils.hasMatchingTag("Action", "Announcement"),
  function (msg)
    if msg.Event == "Started-Waiting-Period" then
      ao.send({Target = ao.id, Action = "AutoPay"})
    elseif (msg.Event == "Tick" or msg.Event == "Started-Game") and not InAction then
      InAction = true
      ao.send({Target = Game, Action = "GetGameState"})
    elseif InAction then
      print("Previous action still in progress. Skipping.")
    end
    print(colors.green .. msg.Event .. ": " .. msg.Data .. colors.reset)
  end
)

Handlers.add(
  "GetGameStateOnTick",
  Handlers.utils.hasMatchingTag("Action", "Tick"),
  function ()
    if not InAction then
      InAction = true
      print(colors.gray .. "Getting game state..." .. colors.reset)
      ao.send({Target = Game, Action = "GetGameState"})
    else
      print("Previous action still in progress. Skipping.")
    end
  end
)

Handlers.add(
  "AutoPay",
  Handlers.utils.hasMatchingTag("Action", "AutoPay"),
  function (msg)
    print("Auto-paying confirmation fees.")
    ao.send({ Target = Game, Action = "Transfer", Recipient = Game, Quantity = "1000"})
  end
)

Handlers.add(
  "UpdateGameState",
  Handlers.utils.hasMatchingTag("Action", "GameState"),
  function (msg)
    local json = require("json")
    LatestGameState = json.decode(msg.Data)
    ao.send({Target = ao.id, Action = "UpdatedGameState"})
    print("Game state updated. Print \'LatestGameState\' for detailed view.")
  end
)

Handlers.add(
  "decideNextAction",
  Handlers.utils.hasMatchingTag("Action", "UpdatedGameState"),
  function ()
    if LatestGameState.GameMode ~= "Playing" then
      InAction = false
      return
    end
    print("Deciding next action.")
    decideNextAction()
    ao.send({Target = ao.id, Action = "Tick"})
  end
)

Handlers.add(
  "ReturnAttack",
  Handlers.utils.hasMatchingTag("Action", "Hit"),
  function (msg)
    if not InAction then
      InAction = true
      local playerEnergy = LatestGameState.Players[ao.id].energy
      if playerEnergy == undefined then
        print(colors.red .. "Unable to read energy." .. colors.reset)
        ao.send({Target = Game, Action = "Attack-Failed", Reason = "Unable to read energy."})
      elseif playerEnergy == 0 then
        print(colors.red .. "Player has insufficient energy." .. colors.reset)
        ao.send({Target = Game, Action = "Attack-Failed", Reason = "Player has no energy."})
      else
        print(colors.red .. "Returning attack." .. colors.reset)
        ao.send({Target = Game, Action = "PlayerAttack", Player = ao.id, AttackEnergy = tostring(playerEnergy)})
      end
      InAction = false
      ao.send({Target = ao.id, Action = "Tick"})
    else
      print("Previous action still in progress. Skipping.")
    end
  end
)

Ctrl + x, Y, 엔터

 

aos

.load bot.lua

 

12.Expanding the Arena

// Ctrl + c 입력 후 밖으로 나온다.
nano ao-effect.lua

// 아래 전체 복사 후 붙여넣기
Width = 40
Height = 40
Range = 1

MaxEnergy = 100
EnergyPerSec = 1

AverageMaxStrengthHitsToKill = 3

function playerInitState()
    return {
        x = math.random(0, Width),
        y = math.random(0, Height),
        health = 100,
        energy = 0
    }
end

function onTick()
    if GameMode ~= "Playing" then return end

    if LastTick == undefined then LastTick = Now end

    local Elapsed = Now - LastTick
    if Elapsed >= 1000 then
        for player, state in pairs(Players) do
            local newEnergy = math.floor(math.min(MaxEnergy, state.energy + (Elapsed * EnergyPerSec // 2000)))
            state.energy = newEnergy
        end
        LastTick = Now
    end
end

function move(msg)
    local playerToMove = msg.From
    local direction = msg.Tags.Direction

    local directionMap = {
        Up = {x = 0, y = -1}, Down = {x = 0, y = 1},
        Left = {x = -1, y = 0}, Right = {x = 1, y = 0},
        UpRight = {x = 1, y = -1}, UpLeft = {x = -1, y = -1},
        DownRight = {x = 1, y = 1}, DownLeft = {x = -1, y = 1}
    }

    if directionMap[direction] then
        local newX = Players[playerToMove].x + directionMap[direction].x
        local newY = Players[playerToMove].y + directionMap[direction].y

        Players[playerToMove].x = (newX - 1) % Width + 1
        Players[playerToMove].y = (newY - 1) % Height + 1

        announce("Player-Moved", playerToMove .. " moved to " .. Players[playerToMove].x .. "," .. Players[playerToMove].y .. ".")
    else
        ao.send({Target = playerToMove, Action = "Move-Failed", Reason = "Invalid direction."})
    end
    onTick()
end

function attack(msg)
    local player = msg.From
    local attackEnergy = tonumber(msg.Tags.AttackEnergy)

    local x = Players[player].x
    local y = Players[player].y

    if Players[player].energy < attackEnergy then
        ao.send({Target = player, Action = "Attack-Failed", Reason = "Not enough energy."})
        return
    end

    Players[player].energy = Players[player].energy - attackEnergy
    local damage = math.floor((math.random() * 2 * attackEnergy) * (1/AverageMaxStrengthHitsToKill))

    announce("Attack", player .. " has launched a " .. damage .. " damage attack from " .. x .. "," .. y .. "!")

    for target, state in pairs(Players) do
        if target ~= player and inRange(x, y, state.x, state.y, Range) then
            local newHealth = state.health - damage
            if newHealth <= 0 then
                eliminatePlayer(target, player)
            else
                Players[target].health = newHealth
                ao.send({Target = target, Action = "Hit", Damage = tostring(damage), Health = tostring(newHealth)})
                ao.send({Target = player, Action = "Successful-Hit", Recipient = target, Damage = tostring(damage), Health = tostring(newHealth)})
            end
        end
    end
end

function inRange(x1, y1, x2, y2, range)
    return x2 >= (x1 - range) and x2 <= (x1 + range) and y2 >= (y1 - range) and y2 <= (y1 + range)
end

Handlers.add("PlayerMove", Handlers.utils.hasMatchingTag("Action", "PlayerMove"), move)

Handlers.add("PlayerAttack", Handlers.utils.hasMatchingTag("Action", "PlayerAttack"), attack)

 

Ctrl + x, Y, 엔터

 

aos

.load ao-effect.lua

 

귀찮지만 잘 따라와서 고생많았다.

 

여기까지 기본적인 Tutorial이 끝났으며

다음편에는 퀘스트를 진행하여 CRED를 얻을 것 이다.

 

다음글 : AO 테스트넷 퀘스트 가이드


 

스팸 댓글이 많아서 본 게시글의 댓글 확인을 안합니다.

궁금하신 점이 있다면 아래 채팅방으로 문의 부탁드립니다.

 

퍼가실 땐 출처 명시 부탁드립니다.

 


 

 

노드 대행 및 기타 문의

graychoi0920@gmail.com


 

노드그레이 텔레그램 공지방

 

노드그레이

그레이의 50가지 노드 그림자

t.me

 

노드그레이 텔레그램 채팅방

 

노드그레이 채팅방

공지방 : https://t.me/nodegray

t.me

 

반응형

'Blockchain > Testnet' 카테고리의 다른 글

Polymer 테스트넷 가이드 1편  (58) 2024.03.12
AO 테스트넷 퀘스트 가이드  (0) 2024.03.05
AO 테스트넷 가이드 3편  (0) 2024.03.05
AO 테스트넷 가이드 2편  (0) 2024.03.05
AO 테스트넷 가이드 1편  (0) 2024.03.04

댓글