제가 플젝으로 말 육성 및 경마 게임을 제작하고 있는데요,
java에선 그렇게 많이 돌아다니는 경마게임 예제가 swift에서는 찾아보기 힘든거 같아
경주부분만 떼어서 공유를 해보려고 합니다.
많이 부족한 코드지만 도움되었으면 좋겠구요 지적이나 오류도 댓글로 남겨주신다면 성실히 답변 해드리겠습니다.
Race 클래스
/**
- arr : 참가한 말들의 배열
- rnak : 참가한 말들이 골인시 순서대로 추가되는 배열
- lenth : 경주 길이
*/
class Race {
var raceProgress: [Int] = []
var rank: [Int] = []
var lenth: Int
/**
- Parameters:
- numOfHorse: 경주에 참여하는 말의 수
- lenth: 경주 길이
*/
init(numOfHorse: Int, lenth: Int){
self.raceProgress = Array(repeating: 0, count: numOfHorse)
self.lenth = lenth
}
/**
해당 트랙의 말이 골인했는지를 반환합니다.
- Parameter index: 말이 달리는 경주로 번호
*/
func horseIsGoal(index i: Int) -> Bool{
let rank = self.rank.firstIndex(of: i + 1)
if rank != nil {
return true
}
return false
}
/**
경주에 참여한 모든 말이 골인해 rank배열이 다 찼다면 true, 경주중이면 false를 반환합니다.
*/
func isEnd() -> Bool{
if rank.count == raceProgress.count {
return true
}
return false
}
/**
해당 인덱스의 말이 달리는 것을 구현합니다
- Parameters:
- index: 말의 경주로 번호
- run: 말이 달릴 칸수
*/
func runHorse(index i: Int, run: Int) -> Bool{
raceProgress[i] += run
if( raceProgress[i] >= lenth) {
raceProgress[i] = lenth
rank.append(i + 1)
return true
}
return false
}
/**
말의 얼마나 달렸는지를 출력합니다. 꽉 찬 사각형이 달린 부분, 빈 박스가 남은 구간입니다.
- Parameter index: 말의 경주로 번호입니다.
*/
func printHores(index i: Int){
print("[\(i + 1)번마]", terminator: "")
var count = raceProgress[i]
while(count > 0){
print("■", terminator: "")
count -= 1
}
count = lenth - raceProgress[i]
while(cㅁount > 0){
print("□", terminator: "")
count -= 1
}
if horseIsGoal(index: i) {
print("[\(rank.firstIndex(of: i + 1)! + 1)]등", terminator: "")
}
print()
}
/**
printHorse를 총 말의 수만큼 출력해 전체 레이스 상황을 출력합니다.
*/
func printAll(){
print("-------------------------------------------")
for i in 0..<raceProgress.count {
printHores(index: i)
}
print("-------------------------------------------")
}
}
setHorse 메서드
/**
말들이 달리는것을 구현한 DIspatchWorkItem의 인스턴스를 반환합니다.
- Parameters:
- horseNum: 경주로 번호입니다.
- race: Race의 인스턴스입니다.
*/
func setHorse(horseNum i:Int, _ race: Race) -> DispatchWorkItem{
let runHorse = DispatchWorkItem{
while(true){
var s: Int = Int.random(in: 1...3)
if race.runHorse(index: i, run: s) {
break
}
s = Int.random(in: 1000000...3000000)
usleep( UInt32(s) )
}
}
return runHorse
}
i번째 트랙의 말이 달리는것을 1~3칸을 달리고 1~3초 쉬게끔 구현한 코드입니다.
작업을 DispatchWorkItem으로 캡슐화해서 반환합니다.
처음에 1~3초를 sleep하게끔 작성하니 동시에 완주하고 rank에 동시에 접근해 정보가 누락되는 에러가 잦았습니다.
그래서 1~3초를 쪼개서 쉬게끔 수정했습니다. 에러가 날 확률이 아예 0%는 아니긴 해서 찝집한 코드입니다...ㅎ
displayRace 메서드
/**
경주상황을 출력해주는 DIspatchWorkItem 인스턴스를 반환합니다.
가장 마지막에 종료되는 스레드로, 동작을 완료한 후 CFrunLoop을 종료시킵니다.
- Parameters:
- race: Race의 인스턴스입니다.
- runLoop: 스레드들을 작동시키는 CFRunLoop의 인스턴스입니다.
*/
func displayRace(_ race: Race, _ runLoop: CFRunLoop) -> DispatchWorkItem{
let displayRace = DispatchWorkItem{
while !race.isEnd() {
race.printAll()
usleep(500000)
}
race.printAll()
print("순위 : \(race.rank)")
CFRunLoopStop(runLoop)
}
return displayRace
}
출력을 담당하는 메서드인데, CFRunLoopStop(runLoop)을 안넣고싶어서 이리저리 해봤지만
아무래도 가장 늦게 종료되는 메서드라 여기에서 runloop를 중지시키는게 흐름에 맞아 넣게 되었습니다.
runloop중에는 스레드가 작업을 마치면 종료가 아니라 대기상태여서 notify도 못받는 터라 어쩔수없이 넣게 되었네요...ㅠ
raceStart 메서드
/**
Race 인스턴스를 생성해 말의 수, 경기장의 길이만큼 스레드들을 작동시킵니다.
- Parameters:
- numOfHorse: 달릴 말들의 수
- lenth: 경기장 길이
*/
func raceStart(numOfHorse: Int, lenth: Int){
let race: Race = Race(numOfHorse: numOfHorse, lenth: lenth)
let runLoop = CFRunLoopGetCurrent()
let raceGroup = DispatchGroup.init()
let raceQueue = DispatchQueue(label: "raceQueue", attributes: .concurrent)
let displayQueue = displayRace(race, runLoop!)
raceQueue.async(group: raceGroup, execute: displayQueue)
for i in 0..<numOfHorse {
let horseQueue = setHorse(horseNum: i, race)
raceQueue.async(group: raceGroup, execute: horseQueue)
}
CFRunLoopRun()
}
race의 인스턴스를 생성하고 작업들을 dispatch queue로 병렬실행시키는 메서드입니다.
원래 work group의 notify를 받아 runloop을 종료시키려고 했지만,
위에서 말한대로 runloop이 종료되고 나서 notify가 오게되서 runloop의 종료는 displayRace메서드에게 맡기게 되었습니다.
이렇게 명시적으로 가장 늦게 끝나는 스레드가 있는게 아닌이상
runLoop.run()에 date나 mode를 입력해 종료시점을 입력하는게 좋은것 같습니다.
setHorse()로부터 말이 달리는것을 구현한 스레드를 DispatchWorkItem으로 캡슐화한 인스턴스를
달리는 말의 수 만큼 반복생성해 reacGroup에 속한 dispatch queue작업인 receQueue로 비동기작업을 진행합니다.
세팅이 완료되면 CFRunLoopRun()으로 dispatch queue작업들을 실행시킵니다.
실행
raceStart(8, 15)
위의 코드는 8마리의 말들이 15칸을 달리는 경주를 실행하겠네요.
실행코드는 간단합니다.
후기
displayRace메서드에 옵저버를 부착해서 루프종료시에 다른 메서드나 스레드가
루프를 종료하게 했다면 좀 더 완벽하지 않았을까...싶어서 조금 아쉽네요.
초기에 NSThread 쓰겠다고 한 시간낭비가 없었으면 시간도 단축됬을것 같구..
본 플젝에선 여기에 [trackNum: horse]인 딕셔너리를 추가해서
말들의 능력치에 따라 usleep 시간과 run을 조정하게끔 구현해뒀습니다.
아마 2~3일 내로 업로드 될 것 같네요.
엄청난 삽질끝에 만들고, 정리한다고 정리했는데도
막상 만들고 보니 좀 조잡하고 엉성해 보이네요..ㅎㅎ ㅠㅠ
swift에서 멀티스레드가 익숙하지 않은 분들에게 도움이 됬으면 좋겠습니다!
전체 코드
import Foundation
/**
- arr : 참가한 말들의 배열
- rnak : 참가한 말들이 골인시 순서대로 append
- lenth : 경주 길이
*/
class Race {
var raceProgress: [Int] = []
var rank: [Int] = []
var lenth: Int
/**
- Parameters:
- numOfHorse: 경주에 참여하는 말의 수
- lenth: 경주 길이
*/
init(numOfHorse: Int, lenth: Int){
self.raceProgress = Array(repeating: 0, count: numOfHorse)
self.lenth = lenth
}
/**
- Parameter index: 말이 달리는 경주로 번호
*/
func horseIsGoal(index i: Int) -> Bool{
let rank = self.rank.firstIndex(of: i + 1)
if rank != nil {
return true
}
return false
}
/**
경주에 참여한 모든 말이 골인해 rank배열이 다 찼다면 true, 경주중이면 false를 반환합니다.
*/
func isEnd() -> Bool{
if rank.count == raceProgress.count {
return true
}
return false
}
/**
해당 인덱스의 말이 달리는 것을 구현합니다
- Parameters:
- index: 말의 경주로 번호
- run: 말이 달릴 칸수
*/
func runHorse(index i: Int, run: Int) -> Bool{
raceProgress[i] += run
if( raceProgress[i] >= lenth) {
raceProgress[i] = lenth
rank.append(i + 1)
return true
}
return false
}
/**
말의 얼마나 달렸는지를 출력합니다. 꽉 찬 사각형이 달린 부분, 빈 박스가 남은 구간입니다.
- Parameter index: 말의 경주로 번호입니다.
*/
func printHores(index i: Int){
print("[\(i + 1)번마]", terminator: "")
var count = raceProgress[i]
while(count > 0){
print("■", terminator: "")
count -= 1
}
count = lenth - raceProgress[i]
while(cㅁount > 0){
print("□", terminator: "")
count -= 1
}
if horseIsGoal(index: i) {
print("[\(rank.firstIndex(of: i + 1)! + 1)]등", terminator: "")
}
print()
}
/**
printHorse를 총 말의 수만큼 출력해 전체 레이스 상황을 출력합니다.
*/
func printAll(){
print("-------------------------------------------")
for i in 0..<raceProgress.count {
printHores(index: i)
}
print("-------------------------------------------")
}
}
/**
Race 인스턴스를 생성해 말의 수, 경기장의 길이만큼 스레드들을 작동시킵니다.
- Parameters:
- numOfHorse: 달릴 말들의 수
- lenth: 경기장 길이
*/
func raceStart(numOfHorse: Int, lenth: Int){
let race: Race = Race(numOfHorse: numOfHorse, lenth: lenth)
let runLoop = CFRunLoopGetCurrent()
let raceGroup = DispatchGroup.init()
let raceQueue = DispatchQueue(label: "raceQueue", attributes: .concurrent)
let displayQueue = displayRace(race, runLoop!)
raceQueue.async(group: raceGroup, execute: displayQueue)
for i in 0..<numOfHorse {
let horseQueue = setHorse(horseNum: i, race)
raceQueue.async(group: raceGroup, execute: horseQueue)
}
CFRunLoopRun()
}
/**
경주상황을 출력해주는 DIspatchWorkItem 인스턴스를 반환합니다.
가장 마지막에 종료되는 스레드로, 동작을 완료한 후 CFrunLoop을 종료시킵니다.
- Parameters:
- race: Race의 인스턴스입니다.
- runLoop: 스레드들을 작동시키는 CFRunLoop의 인스턴스입니다.
*/
func displayRace(_ race: Race, _ runLoop: CFRunLoop) -> DispatchWorkItem{
let displayRace = DispatchWorkItem{
while !race.isEnd() {
race.printAll()
usleep(500000)
}
race.printAll()
print("순위 : \(race.rank)")
CFRunLoopStop(runLoop)
}
return displayRace
}
/**
말들이 달리는것을 구현한 DIspatchWorkItem의 인스턴스를 반환합니다.
- Parameters:
- horseNum: 경주로 번호입니다.
- race: Race의 인스턴스입니다.
*/
func setHorse(horseNum i:Int, _ race: Race) -> DispatchWorkItem{
let runHorse = DispatchWorkItem{
while(true){
var s: Int = Int.random(in: 1...3)
if race.runHorse(index: i, run: s) {
break
}
s = Int.random(in: 1000000...3000000)
usleep( UInt32(s) )
}
}
return runHorse
}
'Language > Swift' 카테고리의 다른 글
Swift에서 정규식 사용하기 (0) | 2021.04.20 |
---|---|
[Swift] 고차함수 Map, Filter, Reduce (0) | 2021.04.05 |
Swift GCD(Grand Central Dispatch) [5] - Group, WorkItem (0) | 2021.03.23 |
Swift GCD(Grand Central Dispatch) [4] - RunLoop 실행과 종료 (0) | 2021.03.23 |
Swift GCD(Grand Central Dispatch) [3] - 예제 사용해보기 (0) | 2021.03.22 |