在我們開始解決問題之前,讓我們從一些測試開始 - 這將給出最終解決方案的定義,並讓我們確信我們正在得到正確的解決方案。
我們最終所追求的是一種功能;我們將其稱爲realTimeToGameTime
。它需要一個Date
對象,並返回一個類似{ day: 0, time: '7:00 am' }
的對象。在你的問題中,你沒有提及是否知道它在遊戲中的哪一天是很重要的(只是在什麼時間),但這不僅僅是一個有用的補充,你會看到它對解決方案很重要。
現在我們知道最終結果的樣子,我們可以考慮一些測試。實時和遊戲時間都有一點表示遊戲時間和實時之間的對應關係。實時,它可以是我們想要的任何時間點;我會叫它c
。在遊戲時間,我們將在第0天的早上7點任意調用它。這允許我們構建一個時間線,爲我們提供我們測試用例的輸入和輸出(分鐘和小時/分鐘):
c+0m c+100m c+200m c+215m c+245m c+345m c+445m c+490m
c+0h c+1:40h c+3:20h c+3:35h c+4:05h c+5:45h c+7:25h c+8:10h
---+--------+--------+--------+--------+--------+--------+---------+---
7:00a 2:30p 10:00p 1:00a 7:00a 2:30p 10:00p 7:00a
day 0 day 1 day 2
我們會挑選一個任意日期/時間 - 午夜,2017年1月1日 - 寫我們的測試:
const y = 2017
const m = 0 // 0=Jan in JS
const d = 1
expect(realTimeToGameTime(new Date(y, m, d, 0, 0))
.toEqual({ day: 0, time: '7:00 am' })
expect(realTimeToGameTime(new Date(y, m, d, 1, 40))
.toEqual({ day: 0, time: '2:30 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 3, 20))
.toEqual({ day: 0, time: '10:00 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 3, 35))
.toEqual({ day: 0, time: '1:00 am' })
expect(realTimeToGameTime(new Date(y, m, d, 4, 50))
.toEqual({ day: 1, time: '7:00 am' })
expect(realTimeToGameTime(new Date(y, m, d, 5, 45))
.toEqual({ day: 1, time: '2:30 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 7, 25))
.toEqual({ day: 1, time: '10:00 pm' })
expect(realTimeToGameTime(new Date(y, m, d, 8, 50))
.toEqual({ day: 2, time: '7:00 am' })
(在這個例子中,我使用的是玩笑期望斷言,你可以定製到無論你喜歡什麼樣的測試/斷言框架......只要確保它有一種方法來測試對象上的值相等。)
現在到解決方案。
遇到這樣的問題時 - 可能會非常快速地引起混淆,特別是當您考慮所有實現細節時 - 最好查看更易於理解的簡化圖片。你選擇的數字很難理解所需的操作,因爲有很多細節:幾分鐘到幾小時,JavaScript喜歡毫秒等等。所以讓我們從一個簡單的圖片開始,接近你的例子,但是更容易想一想(不用擔心:它會推廣到您的特定需求)。
比方說,我們只關心幾小時,遊戲中的白天(7:00a-9:59p)實時持續3小時,而夜間(10:00p-6:59a)持續1小時實時。在這個簡化的模型,我們只關心小時,我們有一個非常巧妙的時間表:
c+0h c+1h c+2h c+3h c+4h c+5h c+6h c+7h c+8h
----+-------+------+-------+------+------+-------+-------+------+-----
7:00a 12:00p 5:00p 10:00p 7:00a 12:00p 5:00p 10:00p 7:00a
day 0 day 1 day 2
的問題是更容易保持在現在我們的頭,但要得到最終的解決方案似乎仍然艱鉅。但是,在更困難的問題中嵌入了一個難以解決的問題:從遊戲世界新一天的黎明開始,在特定的真實世界持續時間之後,真實遊戲世界中的什麼時間?更簡單的是,讓我們將其限制在遊戲世界的某一天。解決即問題很簡單。然而,在我們這樣做之前,我們需要一些輸入。以下是相關投入(我用「天」在這裏指的是24小時內,從「白天」區分開來):
const gameDaybreak = 7
const gameDayLength = 24
const gameDaytimeLength = 15
const gameNighttimeLength = gameDayLength - gameDaytimeLength
const gameDayLengthInRealTime = 4
const gameDaytimeLengthInRealTime = 3
const gameNighttimeLengthInRealTime =
gameDayLengthInRealTime - gameDaytimeLengthInRealTime
現在我們可以寫我們的功能:
gameTimeSinceDaybreakFromRealDuration(realDuration) {
if(realDuration > gameDayLengthInRealTime)
throw new Error("this function only works within a single game day")
if(realDuration <= gameDaytimeLengthInRealTime) {
return (gameDaybreak + realDuration * gameDaytimeLength/
gameDaytimeLengthInRealTime) % gameDayLength
} else {
return (gameDaybreak + gameDaytimeLength +
(realDuration - gameDaytimeLengthInRealTime) *
gameNighttimeLength/gameNighttimeLengthInRealTime) %
gameDayLength
}
}
剩下的只有一塊拼圖:如何確定它是什麼遊戲日,以及遊戲日在什麼時候開始。
幸運的是,我們知道每個遊戲日都會消耗特定量的實際時間(在我們的簡化示例中爲4小時)。因此,如果我們有任意的對應日期,我們可以簡單地將該日期的小時數除以4,併到達(小數)遊戲日。我們只是利用了樓層,以獲得整場比賽天數:
const c = new Date(2017, 0, 1) // this can be anything
const realHoursSinceC = (realDate.valueOf() - c.valueOf())/1000/60/60
const gameDayFromRealDate = Math.floor(realHoursSinceC/gameDayLengthInRealTime)
(因爲我們不會使用小時,因爲我們的時間的基礎上,此功能將不會在我們的最終執行完全一樣的。)
最後,要找出有多少(實際)小時,我們到的那天,我們就可以減去一個代表過去的遊戲天的時間:
const realHoursIntoGameDay = (realDate.valueOf() - c.valueOf()) -
gameDayFromRealDate * gameDayLengthInRealTime
(我們實際上是一個小更高效這裏這些最後兩個計算,但這是更清楚一點。)
以下是我們剛剛完成的真正魔法:我們一直在將所有內容都看作是小時,因此更容易理解。 但是我們所做的一切都是在任意時間點和持續時間內以相同的單位。 JavaScript喜歡毫秒......所以我們所要做的就是使用毫秒而不是幾個小時,並且我們有一個通用的解決方案!
最後一個微小的細節,我們需要格式化,以獲得遊戲時間到一個很好的格式:
function formatGameTime(gameTime) {
const totalMinutes = gameTime/1000/60 // convert from milliseconds to minutes
const hours = Math.floor(totalMinutes/60)
let minutes = (totalMinutes % 60).toFixed(0)
if(minutes.length < 2) minutes = "0" + minutes // get that leading zero!
return hours < 13
? (hours + ':' + minutes + ' am')
: ((hours - 12) + ':' + minutes + ' pm')
}
現在我們要做的就是把它放在一起:
realTimeToGameTime(realTime) {
const gameDayFromRealDate = Math.floor((realTime.valueOf() - c.valueOf()/gameDayLengthInRealTime)
const realTimeIntoGameDay = (realTime.valueOf() - c.valueOf()) - gameDayFromRealDate *
gameDayLengthInRealTime
const gameTime = this.gameTimeSinceDaybreakFromRealDuration(realTimeIntoGameDay)
return {
day: gameDayFromRealDate,
time: this.formatGameTime(gameTime),
}
},
最後,因爲我真的更喜歡純函數 - 全局變量不是很好!我們可以創建一個工廠來創建一個特定的「時間映射器」(請注意這是使用一些ES2015功能):
function createTimeMapper(config) {
const {
c,
gameDaybreak,
gameDayLength,
gameDaytimeLength,
gameNighttimeLength,
gameDayLengthInRealTime,
gameDaytimeLengthInRealTime,
gameNighttimeLengthInRealTime,
} = config
return {
realTimeToGameTime(realTime) {
const gameDayFromRealDate = Math.floor((realTime.valueOf() - c.valueOf())/gameDayLengthInRealTime)
const realTimeIntoGameDay = (realTime.valueOf() - c.valueOf()) - gameDayFromRealDate * gameDayLengthInRealTime
const gameTime = this.gameTimeSinceDaybreakFromRealDuration(realTimeIntoGameDay)
return {
day: gameDayFromRealDate,
time: this.formatGameTime(gameTime),
}
},
gameTimeSinceDaybreakFromRealDuration(realDuration) {
if(realDuration > gameDayLengthInRealTime)
throw new Error("this function only works within a single game day")
if(realDuration <= gameDaytimeLengthInRealTime) {
return (gameDaybreak + realDuration * gameDaytimeLength/gameDaytimeLengthInRealTime) %
gameDayLength
} else {
return (gameDaybreak + gameDaytimeLength +
(realDuration - gameDaytimeLengthInRealTime) * gameNighttimeLength/gameNighttimeLengthInRealTime) %
gameDayLength
}
},
formatGameTime(gameTime) {
const totalMinutes = gameTime/1000/60 // convert from milliseconds to minutes
const hours = Math.floor(totalMinutes/60)
let minutes = (totalMinutes % 60).toFixed(0)
if(minutes.length < 2) minutes = "0" + minutes // get that leading zero!
return hours < 13
? (hours + ':' + minutes + ' am')
: ((hours - 12) + ':' + minutes + ' pm')
},
}
}
,並使用它:在JS
const timeMapper = createTimeMapper({
new Date(2017, 0, 1),
gameDaybreak: 7*1000*60*60,
gameDayLength: 24*1000*60*60,
gameDaytimeLength: 15*1000*60*60,
gameNighttimeLength: 9*1000*60*60,
gameDayLengthInRealTime: (245/60)*1000*60*60,
gameDaytimeLengthInRealTime: (200/60)*1000*60*60,
gameNighttimeLengthInRealTime: (45/60)*1000*60*60,
})
您不能設置系統時間。 – Bergi
[JavaScript代碼運行時鐘(日期和時間)快四倍]的可能重複](http://stackoverflow.com/q/14272133/1048572)? – Bergi