package game
-var ChanceCards = [...]string{
- 0: "Advance to St. Charles Place. If you pass Go, collect $200.",
- 1: "Advance to the nearest Railroad. If unowned, you may buy it from the Bank. If owned, pay owner twice the rental to which they are otherwise entitled.",
- 2: "Advance to the nearest Railroad. If unowned, you may buy it from the Bank. If owned, pay owner twice the rental to which they are otherwise entitled.",
- 3: "Advance token to nearest Utility. If unowned, you may buy it from the Bank. If owned, throw dice and pay owner a total ten times amount thrown.",
- 4: "Go to Jail. Go directly to Jail, do not pass Go, do not collect $200.",
- 5: "Take a trip to Reading Railroad. If you pass Go, collect $200.",
- 6: "Advance to Go (Collect $200)",
- 7: "Get Out of Jail Free.",
- 8: "Advance to Boardwalk.",
- 9: "Your building loan matures. Collect $150.",
- 10: "Go Back 3 Spaces.",
- 11: "Speeding fine $15.",
- 12: "Advance to Illinois Avenue. If you pass Go, collect $200.",
- 13: "Make general repairs on all your property. For each house pay $25. For each hotel pay $100.",
- 14: "You have been elected Chairman of the Board. Pay each player $50.",
- 15: "Bank pays you dividend of $50.",
-}
-
-func GetPlayerMoveDistance(start int32, dest int32) int32 {
- distance := dest - start
- if distance < 0 {
- distance += int32(len(BoardSpaces))
+func (ctx *Context) getOwnerID(sID SpaceID) PlayerID {
+ for _, oP := range ctx.Properties.Owners {
+ if oP.SpaceID == sID {
+ return oP.OwnerID
+ }
}
- return distance
+ return BankPlayerID
}
-func ProcessChance() {
- for _, visitorID := range ChanceVisitors {
- card := RandSrc.IntN(len(ChanceCards))
+func (ctx *Context) ProcessChance() {
+ for _, visitorID := range ctx.Visitors.Chance {
+ card := ctx.Random.IntN(len(ChanceCards))
- currentPos := Users[visitorID].CurrentSpaceID
+ currentPos := ctx.Players.Alive[visitorID.Index()].CurrentSpaceID
switch card {
case 0:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, StCharlesPlaceSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.StCharlesPlace))
case 1, 2:
for i := range BoardSpaces {
offset := int32(i)
- next_pos := currentPos + offset
- propertyType := BoardSpaces[next_pos]
- if propertyType == TypeRailroad {
+ next_pos := Add(currentPos, offset)
+ prop := BoardSpaces[next_pos.Index()]
+ if prop.PropertyType == TypeRailroad {
distance := GetPlayerMoveDistance(currentPos, next_pos)
- if PropertyOwners[SpaceToOwnableProperty[CalculateNextPos(currentPos, distance)]] != visitorID {
- ModifierRailroadRentMultiplier = 2
+
+ if ctx.getOwnerID(CalculateNextPos(currentPos, distance)) != visitorID {
+ ctx.Turn.Modifier.RailroadRentMultiplier = 2
}
- MoveQueue = append(MoveQueue, distance)
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, distance)
+ break
}
}
case 3:
for i := range BoardSpaces {
offset := int32(i)
- next_pos := currentPos + offset
- propertyType := BoardSpaces[next_pos]
- if propertyType == TypeUtility {
+ next_pos := Add(currentPos, offset)
+ prop := BoardSpaces[next_pos.Index()]
+ if prop.PropertyType == TypeUtility {
distance := GetPlayerMoveDistance(currentPos, next_pos)
- if PropertyOwners[SpaceToOwnableProperty[CalculateNextPos(currentPos, distance)]] != visitorID {
- ModifierUtilityForceRentMultiplier = true
+
+ if ctx.getOwnerID(CalculateNextPos(currentPos, distance)) != visitorID {
+ ctx.Turn.Modifier.UtilityForceRentMultiplier = true
}
- MoveQueue = append(MoveQueue, distance)
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, distance)
+ break
}
}
case 4:
- InJailVisitors = append(InJailVisitors, InJailVisitor{visitorID: visitorID, turns: DEFAULT_JAIL_TURNS})
+ ctx.Visitors.InJail = append(ctx.Visitors.InJail, InJailVisitor{visitorID: visitorID, TurnsLeft: JailDefaultTurns})
case 5:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, ReadingRailroadSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.ReadingRailroad))
case 6:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, GoSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.Go))
case 7:
- Users[visitorID].GetOutOfJailCards++
+ ctx.Players.Alive[ctx.Turn.Current.Index()].GetOutOfJailCards++
case 8:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, BoardwalkSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.Boardwalk))
case 9:
- AdjustPlayerMoney(visitorID, 150)
+ ctx.AdjustPlayerMoney(visitorID, 150)
case 10:
- MoveQueue = append(MoveQueue, -3)
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, -3)
case 11:
- AdjustPlayerMoney(visitorID, -15)
+ ctx.AdjustPlayerMoney(visitorID, -15)
case 12:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, IllinoisAvenueSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.IllinoisAvenue))
case 13:
var repairCost int32 = 0
- for propID, ownerID := range PropertyOwners {
- if ownerID == visitorID && OwnablePropertyType[propID] == TypeColor {
- colorID := OwnableToRespProperty[int32(propID)]
+ for _, prop := range ctx.Properties.Owners {
+ ownerID := prop.OwnerID
+ spaceID := prop.SpaceID
+ space := BoardSpaces[spaceID.Index()]
+
+ if ownerID == visitorID && space.PropertyType == TypeColor {
+ colorID := space.SubIndexID
colorProp := ColorProperties[colorID]
- if colorProp.Houses > MAX_PROP_HOUSES { // its a hotel
+ if colorProp.Houses > ColorMaxHouses { // its a hotel
repairCost += 100
} else {
repairCost += colorProp.Houses * 25
}
}
- AdjustPlayerMoney(visitorID, -repairCost)
+ ctx.AdjustPlayerMoney(visitorID, -repairCost)
case 14:
- for i := range Users {
- pID := int32(i)
+ for i := range ctx.Players.Alive {
+ pID := PlayerID{id: int32(i)}
if pID == visitorID {
- AdjustPlayerMoney(pID, -50*int32(len(Users)-1))
+ ctx.AdjustPlayerMoney(pID, -50*int32(len(ctx.Players.Alive)-1))
} else {
- AdjustPlayerMoney(pID, 50)
+ ctx.AdjustPlayerMoney(pID, 50)
}
}
case 15:
- AdjustPlayerMoney(visitorID, 50)
+ ctx.AdjustPlayerMoney(visitorID, 50)
}
}
}
package game
-var ChestCards = [...]string{
- 0: "Advance to Go. (Collect $200)",
- 1: "Bank error in your favor. Collect $200.",
- 2: "Holiday fund matures. Receive $100.",
- 3: "Life insurance matures. Collect $100.",
- 4: "You inherit $100.",
- 5: "From sale of stock you get $50.",
- 6: "Income tax refund. Collect $20.",
- 7: "It is your birthday. Collect $10 from every player.",
- 8: "You are assessed for street repair. $40 per house. $115 per hotel.",
- 9: "Pay hospital fees of $100.",
- 10: "Pay school fees of $50.",
- 11: "Doctor’s fee. Pay $50.",
- 12: "Receive $25 consultancy fee.",
- 13: "Get Out of Jail Free.",
- 14: "Go to Jail. Go directly to jail, do not pass Go, do not collect $200.",
- 15: "You have won second prize in a beauty contest. Collect $10.",
-}
-
-func ProcessChest() {
- for _, visitorID := range ChestVisitors {
- card := RandSrc.IntN(len(ChanceCards))
+func (ctx *Context) ProcessChest() {
+ for _, visitorID := range ctx.Visitors.Chest {
+ card := ctx.Random.IntN(len(ChanceCards))
- currentPos := Users[visitorID].CurrentSpaceID
+ currentPos := ctx.Players.Alive[visitorID.Index()].CurrentSpaceID
switch card {
case 0:
- MoveQueue = append(MoveQueue, GetPlayerMoveDistance(currentPos, GoSpaceID))
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, GetPlayerMoveDistance(currentPos, SpecialSpaces.Go))
case 1:
- AdjustPlayerMoney(visitorID, 200)
+ ctx.AdjustPlayerMoney(visitorID, 200)
case 2:
- AdjustPlayerMoney(visitorID, 100)
+ ctx.AdjustPlayerMoney(visitorID, 100)
case 3:
- AdjustPlayerMoney(visitorID, 100)
+ ctx.AdjustPlayerMoney(visitorID, 100)
case 4:
- AdjustPlayerMoney(visitorID, 100)
+ ctx.AdjustPlayerMoney(visitorID, 100)
case 5:
- AdjustPlayerMoney(visitorID, 50)
+ ctx.AdjustPlayerMoney(visitorID, 50)
case 6:
- AdjustPlayerMoney(visitorID, 20)
+ ctx.AdjustPlayerMoney(visitorID, 20)
case 7:
- for i := range Users {
- pID := int32(i)
+ for i := range ctx.Players.Alive {
+ pID := PlayerID{id: int32(i)}
if pID == visitorID {
- AdjustPlayerMoney(pID, 10*int32(len(Users)-1))
+ ctx.AdjustPlayerMoney(pID, 10*int32(len(ctx.Players.Alive)-1))
} else {
- AdjustPlayerMoney(pID, -10)
+ ctx.AdjustPlayerMoney(pID, -10)
}
}
case 8:
var repairCost int32 = 0
- for propID, ownerID := range PropertyOwners {
- if ownerID == visitorID && OwnablePropertyType[propID] == TypeColor {
- colorID := OwnableToRespProperty[int32(propID)]
+ for _, prop := range ctx.Properties.Owners {
+ ownerID := prop.OwnerID
+ spaceID := prop.SpaceID
+ space := BoardSpaces[spaceID.Index()]
+
+ if ownerID == visitorID && space.PropertyType == TypeColor {
+ colorID := space.SubIndexID
colorProp := ColorProperties[colorID]
- if colorProp.Houses > MAX_PROP_HOUSES { // its a hotel
+ if colorProp.Houses > ColorMaxHouses { // its a hotel
repairCost += 100
} else {
repairCost += colorProp.Houses * 25
}
}
- AdjustPlayerMoney(visitorID, -repairCost)
+ ctx.AdjustPlayerMoney(visitorID, -repairCost)
case 9:
- AdjustPlayerMoney(visitorID, -100)
+ ctx.AdjustPlayerMoney(visitorID, -100)
case 10:
- MoveQueue = append(MoveQueue, -50)
+ ctx.Turn.MoveQueue = append(ctx.Turn.MoveQueue, -50)
case 11:
- AdjustPlayerMoney(visitorID, -50)
+ ctx.AdjustPlayerMoney(visitorID, -50)
case 12:
- AdjustPlayerMoney(visitorID, 25)
+ ctx.AdjustPlayerMoney(visitorID, 25)
case 13:
- InJailVisitors = append(InJailVisitors, InJailVisitor{visitorID: visitorID, turns: DEFAULT_JAIL_TURNS})
+ ctx.Visitors.InJail = append(ctx.Visitors.InJail, InJailVisitor{visitorID: visitorID, TurnsLeft: JailDefaultTurns})
case 14:
- Users[visitorID].GetOutOfJailCards++
+ ctx.Players.Alive[ctx.Turn.Current.Index()].GetOutOfJailCards++
case 15:
- AdjustPlayerMoney(visitorID, 10)
+ ctx.AdjustPlayerMoney(visitorID, 10)
}
}
}
package game
-const MAX_PROP_HOUSES = 4
-
-func HasColorMonopoly(playerID int32, targetGroup ColorGroup) bool {
+func (ctx *Context) HasColorMonopoly(playerID PlayerID, targetGroup ColorGroup) bool {
var ownedCount int32
- for propID, ownerID := range PropertyOwners {
- if ownerID == playerID && OwnablePropertyType[int32(propID)] == TypeColor && ColorProperties[OwnableToRespProperty[int32(propID)]].GroupID == targetGroup {
- ownedCount++
+ for _, prop := range ctx.Properties.Owners {
+ ownerID := prop.OwnerID
+
+ if ownerID != playerID {
+ continue
+ }
+
+ spaceID := prop.SpaceID
+ space := BoardSpaces[spaceID.Index()]
+
+ propType := space.PropertyType
+ if propType != TypeColor {
+ continue
+ }
+
+ colorID := space.SubIndexID
+ if ColorProperties[colorID].GroupID != targetGroup {
+ continue
}
+
+ // the property belongs to player, is a color property and has the right color
+ ownedCount++
}
return ownedCount == ColorGroupSizes[targetGroup]
}
-func ProcessOwnedColors() {
- for _, oCV := range OwnedColorVisitors {
+func (ctx *Context) ProcessOwnedColors() {
+ for _, oCV := range ctx.Visitors.Color {
visitorID := oCV.visitorID
ownerID := oCV.ownerID
colorID := oCV.colorID
var rent int32 = prices[prop.Houses]
- if prop.Houses == 0 && HasColorMonopoly(ownerID, prop.GroupID) {
+ if prop.Houses == 0 && ctx.HasColorMonopoly(ownerID, prop.GroupID) {
rent *= 2
}
- AdjustPlayerMoney(visitorID, -rent)
- AdjustPlayerMoney(ownerID, rent)
+ ctx.AdjustPlayerMoney(visitorID, -rent)
+ ctx.AdjustPlayerMoney(ownerID, rent)
}
}
--- /dev/null
+package game
+
+const (
+ StartingDiceRolls int32 = 1
+)
+
+// Board config
+const (
+ TypeGo PropertyType = iota
+ TypeColor
+ TypeChest
+ TypeTax
+ TypeRailroad
+ TypeChance
+ TypeJail
+ TypeUtility
+ TypeParking
+ TypePolice
+)
+
+const (
+ GroupBrown ColorGroup = iota
+ GroupLightBlue
+ GroupPink
+ GroupOrange
+ GroupRed
+ GroupYellow
+ GroupGreen
+ GroupDarkBlue
+)
+
+var ColorGroupSizes = [...]int32{
+ GroupBrown: 2,
+ GroupLightBlue: 3,
+ GroupPink: 3,
+ GroupOrange: 3,
+ GroupRed: 3,
+ GroupYellow: 3,
+ GroupGreen: 3,
+ GroupDarkBlue: 2,
+}
+
+var BoardSpaces = [...]Space{
+ Space{PropertyType: TypeGo, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 0},
+ Space{PropertyType: TypeChest, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 1},
+ Space{PropertyType: TypeTax, SubIndexID: 0},
+ Space{PropertyType: TypeRailroad, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 2},
+ Space{PropertyType: TypeChance, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 3},
+ Space{PropertyType: TypeColor, SubIndexID: 4},
+ Space{PropertyType: TypeJail, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 5},
+ Space{PropertyType: TypeUtility, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 6},
+ Space{PropertyType: TypeColor, SubIndexID: 7},
+ Space{PropertyType: TypeRailroad, SubIndexID: 1},
+ Space{PropertyType: TypeColor, SubIndexID: 8},
+ Space{PropertyType: TypeChest, SubIndexID: 1},
+ Space{PropertyType: TypeColor, SubIndexID: 9},
+ Space{PropertyType: TypeColor, SubIndexID: 10},
+ Space{PropertyType: TypeParking, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 11},
+ Space{PropertyType: TypeChance, SubIndexID: 1},
+ Space{PropertyType: TypeColor, SubIndexID: 12},
+ Space{PropertyType: TypeColor, SubIndexID: 13},
+ Space{PropertyType: TypeRailroad, SubIndexID: 2},
+ Space{PropertyType: TypeColor, SubIndexID: 14},
+ Space{PropertyType: TypeColor, SubIndexID: 15},
+ Space{PropertyType: TypeUtility, SubIndexID: 1},
+ Space{PropertyType: TypeColor, SubIndexID: 16},
+ Space{PropertyType: TypePolice, SubIndexID: 0},
+ Space{PropertyType: TypeColor, SubIndexID: 17},
+ Space{PropertyType: TypeColor, SubIndexID: 18},
+ Space{PropertyType: TypeChest, SubIndexID: 2},
+ Space{PropertyType: TypeColor, SubIndexID: 19},
+ Space{PropertyType: TypeRailroad, SubIndexID: 3},
+ Space{PropertyType: TypeChance, SubIndexID: 2},
+ Space{PropertyType: TypeColor, SubIndexID: 20},
+ Space{PropertyType: TypeTax, SubIndexID: 1},
+ Space{PropertyType: TypeColor, SubIndexID: 21},
+}
+
+const BoardSpacesLen = len(BoardSpaces)
+
+// Color Config
+var ColorProperties = []ColorProperty{
+ {GroupID: GroupBrown, Houses: 0, Name: "Mediterranean Avenue", Price: 60},
+ {GroupID: GroupBrown, Houses: 0, Name: "Baltic Avenue", Price: 60},
+ {GroupID: GroupLightBlue, Houses: 0, Name: "Oriental Avenue", Price: 100},
+ {GroupID: GroupLightBlue, Houses: 0, Name: "Vermont Avenue", Price: 100},
+ {GroupID: GroupLightBlue, Houses: 0, Name: "Connecticut Avenue", Price: 120},
+ {GroupID: GroupPink, Houses: 0, Name: "St. Charles Place", Price: 140},
+ {GroupID: GroupPink, Houses: 0, Name: "States Avenue", Price: 140},
+ {GroupID: GroupPink, Houses: 0, Name: "Virginia Avenue", Price: 160},
+ {GroupID: GroupOrange, Houses: 0, Name: "St. James Place", Price: 180},
+ {GroupID: GroupOrange, Houses: 0, Name: "Tennessee Avenue", Price: 180},
+ {GroupID: GroupOrange, Houses: 0, Name: "New York Avenue", Price: 200},
+ {GroupID: GroupRed, Houses: 0, Name: "Kentucky Avenue", Price: 220},
+ {GroupID: GroupRed, Houses: 0, Name: "Indiana Avenue", Price: 220},
+ {GroupID: GroupRed, Houses: 0, Name: "Illinois Avenue", Price: 240},
+ {GroupID: GroupYellow, Houses: 0, Name: "Atlantic Avenue", Price: 260},
+ {GroupID: GroupYellow, Houses: 0, Name: "Ventnor Avenue", Price: 260},
+ {GroupID: GroupYellow, Houses: 0, Name: "Marvin Gardens", Price: 280},
+ {GroupID: GroupGreen, Houses: 0, Name: "Pacific Avenue", Price: 300},
+ {GroupID: GroupGreen, Houses: 0, Name: "North Carolina Avenue", Price: 300},
+ {GroupID: GroupGreen, Houses: 0, Name: "Pennsylvania Avenue", Price: 320},
+ {GroupID: GroupDarkBlue, Houses: 0, Name: "Park Place", Price: 350},
+ {GroupID: GroupDarkBlue, Houses: 0, Name: "Boardwalk", Price: 400},
+}
+
+var ColorPropertyRents = [][]int32{
+ {2, 10, 30, 90, 160, 250},
+ {4, 20, 60, 180, 320, 450},
+ {6, 30, 90, 270, 400, 550},
+ {6, 30, 90, 270, 400, 550},
+ {8, 40, 100, 300, 450, 600},
+ {10, 50, 150, 450, 625, 750},
+ {10, 50, 150, 450, 625, 750},
+ {12, 60, 180, 500, 700, 900},
+ {14, 70, 200, 550, 750, 950},
+ {14, 70, 200, 550, 750, 950},
+ {16, 80, 220, 600, 800, 1000},
+ {18, 90, 250, 700, 875, 1050},
+ {18, 90, 250, 700, 875, 1050},
+ {20, 100, 300, 750, 925, 1100},
+ {22, 110, 330, 800, 975, 1150},
+ {22, 110, 330, 800, 975, 1150},
+ {24, 120, 360, 850, 1025, 1200},
+ {26, 130, 390, 900, 1100, 1275},
+ {26, 130, 390, 900, 1100, 1275},
+ {28, 150, 450, 1000, 1200, 1400},
+ {35, 175, 500, 1100, 1300, 1500},
+ {50, 200, 600, 1400, 1700, 2000},
+}
+
+// Railroad Config
+var RailroadProperties = []PropertyStatic{
+ {Name: "Reading Railroad", Price: 200},
+ {Name: "Pennsylvania Railroad", Price: 200},
+ {Name: "B.&O. Railroad", Price: 200},
+ {Name: "Short Line", Price: 200},
+}
+
+const RailroadPrice int32 = 200
+
+var RailroadRent = [...]int32{25, 50, 100, 200}
+
+const RailroadMortgageValue int32 = 100
+
+// Utility Config
+var UtilityProperties = []PropertyStatic{
+ {Name: "Electric Company", Price: 150},
+ {Name: "Waterworks", Price: 150},
+}
+
+const UtilityPrice int32 = 150
+
+var UtilityRentMult = [...]int32{4, 10}
+
+const UtilityMortgageValue int32 = 75
+
+// Tax Config
+var TaxSpaces = []TaxSpace{
+ {Name: "Income Tax", Amount: 200},
+ {Name: "Luxury Tax", Amount: 100},
+}
+
+var BankPlayerID PlayerID = PlayerID{id: -1}
+
+var SpecialSpaces ChanceSpaceIDs
+
+func init() {
+ for i, s := range BoardSpaces {
+ spaceID := SpaceID{id: int32(i)}
+
+ propertyType := s.PropertyType
+
+ switch propertyType {
+ case TypeColor:
+ switch ColorProperties[s.SubIndexID].Name {
+ case "St. Charles Place":
+ SpecialSpaces.StCharlesPlace = spaceID
+ case "Boardwalk":
+ SpecialSpaces.Boardwalk = spaceID
+ case "Illinois Avenue":
+ SpecialSpaces.IllinoisAvenue = spaceID
+ }
+ case TypeRailroad:
+ if RailroadProperties[s.SubIndexID].Name == "Reading Railroad" {
+ SpecialSpaces.ReadingRailroad = spaceID
+ }
+ case TypeGo:
+ SpecialSpaces.Go = spaceID
+ }
+ }
+}
+
+const JailDefaultTurns int32 = 3
+const JailBuyoutCost int32 = 50
+
+const GoSalary int32 = 200
+
+const ColorMaxHouses = 4
+
+const StartingMoney int32 = 1500
+const StartingGetOutOfJailFreeCards int32 = 0
+
+var ChestCards = [...]string{
+ 0: "Advance to Go. (Collect $200)",
+ 1: "Bank error in your favor. Collect $200.",
+ 2: "Holiday fund matures. Receive $100.",
+ 3: "Life insurance matures. Collect $100.",
+ 4: "You inherit $100.",
+ 5: "From sale of stock you get $50.",
+ 6: "Income tax refund. Collect $20.",
+ 7: "It is your birthday. Collect $10 from every player.",
+ 8: "You are assessed for street repair. $40 per house. $115 per hotel.",
+ 9: "Pay hospital fees of $100.",
+ 10: "Pay school fees of $50.",
+ 11: "Doctor’s fee. Pay $50.",
+ 12: "Receive $25 consultancy fee.",
+ 13: "Get Out of Jail Free.",
+ 14: "Go to Jail. Go directly to jail, do not pass Go, do not collect $200.",
+ 15: "You have won second prize in a beauty contest. Collect $10.",
+}
+
+var ChanceCards = [...]string{
+ 0: "Advance to St. Charles Place. If you pass Go, collect $200.",
+ 1: "Advance to the nearest Railroad. If unowned, you may buy it from the Bank. If owned, pay owner twice the rental to which they are otherwise entitled.",
+ 2: "Advance to the nearest Railroad. If unowned, you may buy it from the Bank. If owned, pay owner twice the rental to which they are otherwise entitled.",
+ 3: "Advance token to nearest Utility. If unowned, you may buy it from the Bank. If owned, throw dice and pay owner a total ten times amount thrown.",
+ 4: "Go to Jail. Go directly to Jail, do not pass Go, do not collect $200.",
+ 5: "Take a trip to Reading Railroad. If you pass Go, collect $200.",
+ 6: "Advance to Go (Collect $200)",
+ 7: "Get Out of Jail Free.",
+ 8: "Advance to Boardwalk.",
+ 9: "Your building loan matures. Collect $150.",
+ 10: "Go Back 3 Spaces.",
+ 11: "Speeding fine $15.",
+ 12: "Advance to Illinois Avenue. If you pass Go, collect $200.",
+ 13: "Make general repairs on all your property. For each house pay $25. For each hotel pay $100.",
+ 14: "You have been elected Chairman of the Board. Pay each player $50.",
+ 15: "Bank pays you dividend of $50.",
+}
"math/rand/v2"
)
-var RandSeed = rand.NewPCG(20, 26)
-var RandSrc = rand.New(RandSeed)
+func initTurn(pID PlayerID) Turn {
+ return Turn{
+ Current: pID,
+ Ended: false,
+ DiceRollsRemaining: StartingDiceRolls,
+ NumDiceRolled: 0,
+ RolledDoubles: false,
+ MoveQueue: []int32{},
+ InDebt: false,
+ Modifier: Modifiers{
+ RailroadRentMultiplier: 1,
+ UtilityForceRentMultiplier: false,
+ },
+ }
+}
-var Users []User
-var DebtEvents []int32
-var MoveablePlayers []int32
-var MoveQueue []int32
+func InitCtx(randSeed rand.Source, players []Player) *Context {
+ startingPlayerID := PlayerID{id: 0}
+ ownableProps := []OwnableProperty{}
+ for i, s := range BoardSpaces {
+ spaceID := SpaceID{id: int32(i)}
+
+ propertyType := s.PropertyType
+ if propertyType == TypeColor || propertyType == TypeRailroad || propertyType == TypeUtility {
+ ownableProps = append(ownableProps, OwnableProperty{
+ OwnerID: BankPlayerID,
+ SpaceID: spaceID,
+ })
+ }
+ }
+
+ return &Context{
+ Random: rand.New(randSeed),
+ Players: Players{
+ Alive: players,
+ },
+ Turn: initTurn(startingPlayerID),
+ Visitors: Visitors{
+ Unowned: []UnownedPropertyVisitor{},
+ Color: []OwnedColorVisitor{},
+ Railroad: []OwnedRailroadVisitor{},
+ Utility: []OwnedUtilityVisitor{},
+ Go: []PlayerID{},
+ Tax: []TaxVisitor{},
+ Chance: []PlayerID{},
+ Chest: []PlayerID{},
+ InJail: []InJailVisitor{},
+ // Parking: []PlayerID{},
+ // Police: []PlayerID{},
+ },
+ Properties: Properties{
+ Owners: ownableProps,
+ Mortgages: []PropertyID{},
+ },
+ }
+}
-var ModifierRailroadRentMultiplier int32 = 1
-var ModifierUtilityForceRentMultiplier bool = false
+func InitPlayer() Player {
+ return Player{
+ UUID: "abc", // TODO: Generate proper UUID
+ Money: StartingMoney,
+ CurrentSpaceID: SpecialSpaces.Go,
+ GetOutOfJailCards: StartingGetOutOfJailFreeCards,
+ CanMove: true,
+ }
+}
-var TurnPlayerID int32 = 0
-var TurnEndedSignal bool = false
-var DiceRollsRemaining int32 = 1
-var numDiceRolled int32 = 0
-var RolledDoubles bool = false
+func (ctx *Context) GetCurrentTurnPlayer() *Player {
+ return &ctx.Players.Alive[ctx.Turn.Current.Index()]
+}
-func ValidateCanRoll(UUID string) bool {
- if Users[TurnPlayerID].UUID == UUID && DiceRollsRemaining > 0 {
+func (ctx *Context) ValidateIsTurn(UUID string) bool {
+ if ctx.GetCurrentTurnPlayer().UUID == UUID {
return true
}
+ return false
+}
+func (ctx *Context) ValidateCanRoll(UUID string) bool {
+ if ctx.ValidateIsTurn(UUID) && ctx.Turn.DiceRollsRemaining > 0 {
+ return true
+ }
return false
}
-func ValidateCanEndTurn(UUID string) bool {
- if Users[TurnPlayerID].UUID != UUID || DiceRollsRemaining > 0 || Users[TurnPlayerID].Money < 0 {
- return false
+func (ctx *Context) ValidateCanEndTurn(UUID string) bool {
+ if !(ctx.ValidateIsTurn(UUID) || ctx.Turn.DiceRollsRemaining > 0 || ctx.GetCurrentTurnPlayer().Money < 0) {
+ return true
}
- return true
+ return false
}
-func ValidateCanExitJail(UUID string) bool {
- for _, iJV := range InJailVisitors {
- player := Users[iJV.visitorID]
- if Users[TurnPlayerID].UUID == UUID && player.UUID == UUID {
+func (ctx *Context) ValidateCanExitJail(UUID string) bool {
+ for _, iJV := range ctx.Visitors.InJail {
+ if ctx.Players.Alive[iJV.visitorID.Index()].UUID == UUID {
return true
}
}
return false
}
-func ProcessLanding() {
- ProcessGo()
- ProcessTax()
+func (ctx *Context) ProcessLanding() {
+ ctx.ProcessGo()
+ ctx.ProcessTax()
- ProcessOwnedColors()
- ProcessOwnedUtility()
- ProcessOwnedRailroad()
+ ctx.ProcessOwnedColors()
+ ctx.ProcessOwnedUtility()
+ ctx.ProcessOwnedRailroad()
+ ctx.ProcessUnowned()
- ProcessChance()
- ProcessChest()
+ ctx.ProcessChance()
+ ctx.ProcessChest()
// ProcessPolice()
- ProcessJail()
+ ctx.ProcessJail()
}
-func RollDice() {
+func (ctx *Context) RollDice() {
// Roll Dice
- diceRoll1 := RandSrc.Int32N(6) + 1
- diceRoll2 := RandSrc.Int32N(6) + 1
+ diceRoll1 := ctx.Random.Int32N(6) + 1
+ diceRoll2 := ctx.Random.Int32N(6) + 1
- numDiceRolled++
+ ctx.Turn.NumDiceRolled++
+ ctx.Turn.DiceRollsRemaining--
if diceRoll1 == diceRoll2 {
- RolledDoubles = true
- DiceRollsRemaining++
- RemovePlayerFromJail(TurnPlayerID)
+ ctx.Turn.RolledDoubles = true
+ ctx.Turn.DiceRollsRemaining++
+ ctx.RemovePlayerFromJail(ctx.Turn.Current)
}
- if numDiceRolled >= 3 {
- InJailVisitors = append(InJailVisitors, InJailVisitor{visitorID: TurnPlayerID, turns: DEFAULT_JAIL_TURNS})
+ if ctx.Turn.NumDiceRolled >= 3 {
+ ctx.Visitors.InJail = append(ctx.Visitors.InJail, InJailVisitor{visitorID: ctx.Turn.Current, TurnsLeft: JailDefaultTurns})
}
}
-func EndTurn() {
- // next player's turn
- TurnPlayerID = (TurnPlayerID + 1) % int32(len(Users))
- TurnEndedSignal = false
+func (ctx *Context) EndTurn() {
+ nextTurnPlayerID := PlayerID{id: (ctx.Turn.Current.id + 1) % int32(len(ctx.Players.Alive))}
+ ctx.Turn = initTurn(nextTurnPlayerID)
+}
+
+func (ctx *Context) IsMortgaged(propID PropertyID) bool {
+ for _, oPID := range ctx.Properties.Mortgages {
+ if oPID == propID {
+ return true
+ }
+ }
+ return false
+}
- // reset dice
- DiceRollsRemaining = 1
- numDiceRolled = 0
- RolledDoubles = false
+func (ctx *Context) IsOwned(spaceID SpaceID) bool {
+ for _, prop := range ctx.Properties.Owners {
+ if spaceID == prop.SpaceID {
+ if prop.OwnerID == BankPlayerID {
+ return false
+ } else {
+ return true
+ }
+ }
+ }
+ panic("Space is not an ownable property")
+}
- // let player move next turn
- MoveQueue = MoveQueue[:0]
+func (ctx *Context) getPropID(spaceID SpaceID) PropertyID {
+ for i, prop := range ctx.Properties.Owners {
+ if spaceID == prop.SpaceID {
+ return PropertyID{id: int32(i)}
+ }
+ }
+ panic("Space is not an ownable property")
}
package game
-const GO_SALARY int32 = 200
-
-func ProcessGo() {
- for _, playerID := range GoVisitors {
- AdjustPlayerMoney(playerID, GO_SALARY)
+func (ctx *Context) ProcessGo() {
+ for _, playerID := range ctx.Visitors.Go {
+ ctx.AdjustPlayerMoney(playerID, GoSalary)
}
}
package game
-func IsInDebt(playerID int32) (bool, int32) {
- for i, pID := range DebtEvents {
- if pID == playerID {
- return true, int32(i)
- }
- }
- return false, -1
-}
-
-func AdjustPlayerMoney(playerID int32, amount int32) {
- Users[playerID].Money += amount
-
- inDebt, i := IsInDebt(playerID)
+func (ctx *Context) AdjustPlayerMoney(playerID PlayerID, amount int32) {
+ ctx.Players.Alive[playerID.Index()].Money += amount
- if Users[playerID].Money < 0 {
- if !inDebt {
- DebtEvents = append(DebtEvents, playerID)
- }
+ if ctx.Players.Alive[playerID.Index()].Money < 0 {
+ ctx.Turn.InDebt = true
} else { // Money >= 0
- if inDebt { // remove player from DebtEvents table
- DebtEvents[i] = DebtEvents[len(DebtEvents)-1]
- DebtEvents = DebtEvents[:len(DebtEvents)-1]
- }
+ ctx.Turn.InDebt = false
}
}
"errors"
)
-const DEFAULT_JAIL_TURNS int32 = 3
-const JAIL_BUYOUT_COST int32 = 50
-
-func RemovePlayerFromJail(playerID int32) {
- for i, iJV := range InJailVisitors {
+func (ctx *Context) RemovePlayerFromJail(playerID PlayerID) {
+ for i, iJV := range ctx.Visitors.InJail {
if playerID == iJV.visitorID {
- InJailVisitors[i] = InJailVisitors[len(InJailVisitors)-1]
- InJailVisitors = InJailVisitors[:len(InJailVisitors)-1]
+ ctx.Visitors.InJail[i] = ctx.Visitors.InJail[len(ctx.Visitors.InJail)-1]
+ ctx.Visitors.InJail = ctx.Visitors.InJail[:len(ctx.Visitors.InJail)-1]
}
}
- MoveablePlayers = append(MoveablePlayers, playerID)
+ ctx.Players.Alive[playerID.Index()].CanMove = true
}
-func RemovePlayerFromMoveable(pID int32) {
- for i, playerID := range MoveablePlayers {
+func (ctx *Context) RemovePlayerFromMoveable(pID PlayerID) {
+ for i, _ := range ctx.Players.Alive {
+ playerID := PlayerID{id: int32(i)}
if pID == playerID {
- MoveablePlayers[i] = MoveablePlayers[len(MoveablePlayers)-1]
- MoveablePlayers = MoveablePlayers[:len(MoveablePlayers)-1]
+ ctx.Players.Alive[playerID.Index()].CanMove = false
}
}
}
-func ProcessJail() {
- for _, iJV := range InJailVisitors {
+func (ctx *Context) ProcessJail() {
+ for _, iJV := range ctx.Visitors.InJail {
visitorID := iJV.visitorID
- turns := iJV.turns
+ turnsLeft := iJV.TurnsLeft
- if turns <= 0 {
- RemovePlayerFromJail(visitorID)
+ if turnsLeft <= 0 {
+ ctx.RemovePlayerFromJail(visitorID)
} else {
- RemovePlayerFromMoveable(visitorID)
+ ctx.RemovePlayerFromMoveable(visitorID)
}
}
var ErrNotEnoughJailCards = errors.New("Cannot use jail card: player does not have enough get out of jail free cards")
var ErrNotEnoughMoney = errors.New("Cannot execute action: player does not have enough money")
-func JailUseCard() error {
- if Users[TurnPlayerID].GetOutOfJailCards > 0 {
- RemovePlayerFromJail(TurnPlayerID)
- Users[TurnPlayerID].GetOutOfJailCards -= 1
+func (ctx *Context) JailUseCard() error {
+ currID := ctx.Turn.Current
+ if ctx.Players.Alive[currID.Index()].GetOutOfJailCards > 0 {
+ ctx.RemovePlayerFromJail(currID)
+ ctx.Players.Alive[currID.Index()].GetOutOfJailCards -= 1
return nil
} else {
}
}
-func JailBuyout() error {
- if Users[TurnPlayerID].Money >= JAIL_BUYOUT_COST {
- RemovePlayerFromJail(TurnPlayerID)
- AdjustPlayerMoney(TurnPlayerID, -JAIL_BUYOUT_COST)
+func (ctx *Context) JailBuyout() error {
+ currID := ctx.Turn.Current
+ if ctx.Players.Alive[currID.Index()].Money >= JailBuyoutCost {
+ ctx.RemovePlayerFromJail(currID)
+ ctx.AdjustPlayerMoney(currID, -JailBuyoutCost)
return nil
} else {
return ErrNotEnoughMoney
package game
-var BoardSpaces = [...]PropertyType{
- TypeGo,
- TypeColor,
- TypeChest,
- TypeColor,
- TypeTax,
- TypeRailroad,
- TypeColor,
- TypeChance,
- TypeColor,
- TypeColor,
- TypeJail,
- TypeColor,
- TypeUtility,
- TypeColor,
- TypeColor,
- TypeRailroad,
- TypeColor,
- TypeChest,
- TypeColor,
- TypeColor,
- TypeParking,
- TypeColor,
- TypeChance,
- TypeColor,
- TypeColor,
- TypeRailroad,
- TypeColor,
- TypeColor,
- TypeUtility,
- TypeColor,
- TypeJail,
- TypeColor,
- TypeColor,
- TypeChest,
- TypeColor,
- TypeRailroad,
- TypeChance,
- TypeColor,
- TypeTax,
- TypeColor,
-}
-
-type PropertyStatic struct {
- Name string
- Price int32
-}
-
-type ColorGroup int32
-
-const (
- GroupBrown ColorGroup = iota
- GroupLightBlue
- GroupPink
- GroupOrange
- GroupRed
- GroupYellow
- GroupGreen
- GroupDarkBlue
-)
-
-var ColorGroupSizes = [...]int32{
- GroupBrown: 2,
- GroupLightBlue: 3,
- GroupPink: 3,
- GroupOrange: 3,
- GroupRed: 3,
- GroupYellow: 3,
- GroupGreen: 3,
- GroupDarkBlue: 2,
-}
-
-type ColorProperty struct {
- Name string
- Price int32
- GroupID ColorGroup
- Houses int32
-}
-
-var ColorProperties = []ColorProperty{
- {GroupID: GroupBrown, Houses: 0, Name: "Mediterranean Avenue", Price: 60},
- {GroupID: GroupBrown, Houses: 0, Name: "Baltic Avenue", Price: 60},
- {GroupID: GroupLightBlue, Houses: 0, Name: "Oriental Avenue", Price: 100},
- {GroupID: GroupLightBlue, Houses: 0, Name: "Vermont Avenue", Price: 100},
- {GroupID: GroupLightBlue, Houses: 0, Name: "Connecticut Avenue", Price: 120},
- {GroupID: GroupPink, Houses: 0, Name: "St. Charles Place", Price: 140},
- {GroupID: GroupPink, Houses: 0, Name: "States Avenue", Price: 140},
- {GroupID: GroupPink, Houses: 0, Name: "Virginia Avenue", Price: 160},
- {GroupID: GroupOrange, Houses: 0, Name: "St. James Place", Price: 180},
- {GroupID: GroupOrange, Houses: 0, Name: "Tennessee Avenue", Price: 180},
- {GroupID: GroupOrange, Houses: 0, Name: "New York Avenue", Price: 200},
- {GroupID: GroupRed, Houses: 0, Name: "Kentucky Avenue", Price: 220},
- {GroupID: GroupRed, Houses: 0, Name: "Indiana Avenue", Price: 220},
- {GroupID: GroupRed, Houses: 0, Name: "Illinois Avenue", Price: 240},
- {GroupID: GroupYellow, Houses: 0, Name: "Atlantic Avenue", Price: 260},
- {GroupID: GroupYellow, Houses: 0, Name: "Ventnor Avenue", Price: 260},
- {GroupID: GroupYellow, Houses: 0, Name: "Marvin Gardens", Price: 280},
- {GroupID: GroupGreen, Houses: 0, Name: "Pacific Avenue", Price: 300},
- {GroupID: GroupGreen, Houses: 0, Name: "North Carolina Avenue", Price: 300},
- {GroupID: GroupGreen, Houses: 0, Name: "Pennsylvania Avenue", Price: 320},
- {GroupID: GroupDarkBlue, Houses: 0, Name: "Park Place", Price: 350},
- {GroupID: GroupDarkBlue, Houses: 0, Name: "Boardwalk", Price: 400},
-}
-
-var ColorPropertyRents = [][]int32{
- {2, 10, 30, 90, 160, 250},
- {4, 20, 60, 180, 320, 450},
- {6, 30, 90, 270, 400, 550},
- {6, 30, 90, 270, 400, 550},
- {8, 40, 100, 300, 450, 600},
- {10, 50, 150, 450, 625, 750},
- {10, 50, 150, 450, 625, 750},
- {12, 60, 180, 500, 700, 900},
- {14, 70, 200, 550, 750, 950},
- {14, 70, 200, 550, 750, 950},
- {16, 80, 220, 600, 800, 1000},
- {18, 90, 250, 700, 875, 1050},
- {18, 90, 250, 700, 875, 1050},
- {20, 100, 300, 750, 925, 1100},
- {22, 110, 330, 800, 975, 1150},
- {22, 110, 330, 800, 975, 1150},
- {24, 120, 360, 850, 1025, 1200},
- {26, 130, 390, 900, 1100, 1275},
- {26, 130, 390, 900, 1100, 1275},
- {28, 150, 450, 1000, 1200, 1400},
- {35, 175, 500, 1100, 1300, 1500},
- {50, 200, 600, 1400, 1700, 2000},
-}
-
-var RailroadProperties = []PropertyStatic{
- {Name: "Reading Railroad", Price: 200},
- {Name: "Pennsylvania Railroad", Price: 200},
- {Name: "B.&O. Railroad", Price: 200},
- {Name: "Short Line", Price: 200},
-}
-
-const RailroadPrice int32 = 200
-
-var RailroadRent = [...]int32{25, 50, 100, 200}
-
-const RailroadMortgageValue int32 = 100
-
-var UtilityProperties = []PropertyStatic{
- {Name: "Electric Company", Price: 150},
- {Name: "Waterworks", Price: 150},
-}
-
-const UtilityPrice int32 = 150
-
-var UtilityRentMult = [...]int32{4, 10}
-
-const UtilityMortgageValue int32 = 75
-
-type TaxSpace struct {
- Name string
- Amount int32
-}
-
-var TaxSpaces = []TaxSpace{
- {Name: "Income Tax", Amount: 200},
- {Name: "Luxury Tax", Amount: 100},
-}
-
-var PropertyOwners = []int32{} // playerID
-var OwnablePropertyType = []PropertyType{} // uses ownablePropertyID
-var PropertyMortgages = []int32{} // mortgaged ownablePropertyIDs
-
-func IsMortgaged(ownablePropertyID int32) bool {
- for _, oPID := range PropertyMortgages {
- if oPID == ownablePropertyID {
- return true
- }
- }
- return false
-}
-
-var SpaceToRespProperty = make(map[int32]int32)
-var SpaceToOwnableProperty = make(map[int32]int32)
-var SpaceToTaxSpace = make(map[int32]int32)
-
-var OwnableToRespProperty = make(map[int32]int32)
-var RespPropertyToOwnable = make(map[int32]int32)
-
-var StCharlesPlaceSpaceID int32 = 0
-var GoSpaceID int32 = 0
-var ReadingRailroadSpaceID int32 = 0
-var BoardwalkSpaceID int32 = 0
-var IllinoisAvenueSpaceID int32 = 0
-
-func init() {
- var (
- colorIndex int32 = 0
- railroadIndex int32 = 0
- utilityIndex int32 = 0
- taxIndex int32 = 0
- ownableIndex int32 = 0
- )
-
- for i, propertyType := range BoardSpaces {
- spaceID := int32(i)
-
- if propertyType == TypeColor || propertyType == TypeRailroad || propertyType == TypeUtility {
- SpaceToOwnableProperty[spaceID] = ownableIndex
- PropertyOwners = append(PropertyOwners, -1)
- OwnablePropertyType = append(OwnablePropertyType, propertyType)
- }
-
- switch propertyType {
- case TypeColor:
- SpaceToRespProperty[spaceID] = colorIndex
- OwnableToRespProperty[ownableIndex] = colorIndex
- RespPropertyToOwnable[colorIndex] = ownableIndex
-
- switch ColorProperties[colorIndex].Name {
- case "St. Charles Place":
- StCharlesPlaceSpaceID = spaceID
- case "Boardwalk":
- BoardwalkSpaceID = spaceID
- case "Illinois Avenue":
- IllinoisAvenueSpaceID = spaceID
- }
-
- colorIndex++
- case TypeRailroad:
- SpaceToRespProperty[spaceID] = railroadIndex
- OwnableToRespProperty[ownableIndex] = railroadIndex
- RespPropertyToOwnable[railroadIndex] = ownableIndex
- if ColorProperties[colorIndex].Name == "Reading Railroad" {
- ReadingRailroadSpaceID = spaceID
- }
- railroadIndex++
- case TypeUtility:
- SpaceToRespProperty[spaceID] = utilityIndex
- OwnableToRespProperty[ownableIndex] = utilityIndex
- RespPropertyToOwnable[utilityIndex] = ownableIndex
- utilityIndex++
- case TypeTax:
- SpaceToTaxSpace[spaceID] = taxIndex
- taxIndex++
- case TypeGo:
- GoSpaceID = spaceID
- }
-
- ownableIndex++
-
+func GetPlayerMoveDistance(start SpaceID, dest SpaceID) int32 {
+ distance := dest.id - start.id
+ if distance < 0 {
+ distance += int32(len(BoardSpaces))
}
-
-}
-
-type OwnedColorVisitor struct {
- visitorID int32
- ownerID int32
- colorID int32
-}
-
-type OwnedRailroadVisitor struct {
- visitorID int32
- ownerID int32
- railroadID int32
-}
-
-type OwnedUtilityVisitor struct {
- visitorID int32
- ownerID int32
- utilityID int32
- diceRoll int32
-}
-
-type UnownedPropertyVisitor struct {
- visitorID int32
- propertyID int32
+ return distance
}
-type InJailVisitor struct {
- visitorID int32
- turns int32
+func (ctx *Context) AllowedToMove(playerID PlayerID) bool {
+ return ctx.Players.Alive[playerID.Index()].CanMove
}
-var (
- UnownedPropertyVisitors []UnownedPropertyVisitor
- OwnedColorVisitors []OwnedColorVisitor
- OwnedRailroadVisitors []OwnedRailroadVisitor
- OwnedUtilityVisitors []OwnedUtilityVisitor
- GoVisitors []int32
- TaxVisitors []int32
- ChanceVisitors []int32
- ChestVisitors []int32
- InJailVisitors []InJailVisitor
- ParkingVisitors []int32
- PoliceVisitors []int32
-)
-
-func AllowedToMove(playerID int32) bool {
- for _, pID := range MoveablePlayers {
- if playerID == pID {
- return true
- }
- }
- return false
-}
-
-func ProcessMovement() {
- for i, playerID := range MoveablePlayers {
- if playerID == TurnPlayerID {
- // Movement
- for {
- // condition to stop moving
- if len(MoveQueue) == 0 {
- // player can no longer move
- MoveablePlayers[i] = MoveablePlayers[len(MoveablePlayers)-1]
- MoveablePlayers = MoveablePlayers[:len(MoveablePlayers)-1]
- break
- }
-
- dist := MoveQueue[0]
- MoveQueue = MoveQueue[1:]
- player := Users[playerID]
- AdvancePlayer(playerID, player.CurrentSpaceID, dist)
-
- ProcessLanding()
- }
-
- }
+func (ctx *Context) ProcessMovement() {
+ cID := ctx.Turn.Current
+ if ctx.AllowedToMove(cID) {
+ dist := ctx.Turn.MoveQueue[0]
+ ctx.Turn.MoveQueue = ctx.Turn.MoveQueue[1:]
+ ctx.AdvancePlayer(cID, ctx.Players.Alive[cID.Index()].CurrentSpaceID, dist)
+ ctx.ProcessLanding()
}
}
-func CalculateNextPos(currentPosition int32, distance int32) int32 {
- nextPos := (currentPosition + distance)
- nextPos %= int32(len(BoardSpaces))
+func CalculateNextPos(currentPosition SpaceID, distance int32) SpaceID {
+ nextPos := Add(currentPosition, distance)
+ nextPos.id %= int32(len(BoardSpaces))
return nextPos
}
-func AdvancePlayer(playerID int32, currentPosition int32, diceRoll int32) {
+func (ctx *Context) AdvancePlayer(playerID PlayerID, currentPosition SpaceID, diceRoll int32) {
nextPos := CalculateNextPos(currentPosition, diceRoll)
- numGoPasses := (currentPosition + diceRoll) / (int32(len(BoardSpaces)) - 1)
+ numGoPasses := Add(currentPosition, diceRoll).id / int32(len(BoardSpaces))
+
if numGoPasses > 0 {
+ if BoardSpaces[nextPos.Index()].PropertyType == TypeGo {
+ numGoPasses--
+ }
for range numGoPasses {
- GoVisitors = append(GoVisitors, playerID)
+ ctx.Visitors.Go = append(ctx.Visitors.Go, playerID)
}
}
- propType := BoardSpaces[nextPos]
+ prop := BoardSpaces[nextPos.Index()]
- switch propType {
+ switch prop.PropertyType {
case TypeGo:
- GoVisitors = append(GoVisitors, playerID)
+ ctx.Visitors.Go = append(ctx.Visitors.Go, playerID)
case TypeChest:
- ChestVisitors = append(ChestVisitors, playerID)
+ ctx.Visitors.Chest = append(ctx.Visitors.Chest, playerID)
case TypeChance:
- ChanceVisitors = append(ChanceVisitors, playerID)
+ ctx.Visitors.Chance = append(ctx.Visitors.Chance, playerID)
case TypeTax:
- TaxVisitors = append(TaxVisitors, playerID)
- // case TypeParking: // nothing ever happens
- // ParkingVisitors = append(ParkingVisitors, playerID)
+ ctx.Visitors.Tax = append(ctx.Visitors.Tax, TaxVisitor{visitorID: playerID, taxID: prop.SubIndexID})
case TypePolice: // hardcoding to send straight to jail
- InJailVisitors = append(InJailVisitors, InJailVisitor{visitorID: playerID, turns: DEFAULT_JAIL_TURNS})
+ ctx.Visitors.InJail = append(ctx.Visitors.InJail, InJailVisitor{visitorID: playerID, TurnsLeft: JailDefaultTurns})
case TypeJail:
- InJailVisitors = append(InJailVisitors, InJailVisitor{visitorID: playerID, turns: DEFAULT_JAIL_TURNS})
+ ctx.Visitors.InJail = append(ctx.Visitors.InJail, InJailVisitor{visitorID: playerID, TurnsLeft: JailDefaultTurns})
case TypeColor:
- propIndex := SpaceToOwnableProperty[nextPos]
- if PropertyOwners[propIndex] != -1 { // property owned?
- ownerID := PropertyOwners[propIndex]
- if ownerID != playerID && !IsMortgaged(propIndex) { // not by you
- OwnedColorVisitors = append(OwnedColorVisitors, OwnedColorVisitor{visitorID: playerID, ownerID: ownerID, colorID: SpaceToRespProperty[nextPos]})
+ propID := ctx.getPropID(nextPos)
+ ownerID := ctx.Properties.Owners[propID.Index()].OwnerID
+ if ownerID != BankPlayerID { // property owned?
+ if ownerID != playerID && !ctx.IsMortgaged(propID) { // not by you
+ ctx.Visitors.Color = append(ctx.Visitors.Color, OwnedColorVisitor{visitorID: playerID, ownerID: ownerID, colorID: prop.SubIndexID})
}
} else {
- UnownedPropertyVisitors = append(UnownedPropertyVisitors, UnownedPropertyVisitor{visitorID: playerID, propertyID: propIndex})
+ ctx.Visitors.Unowned = append(ctx.Visitors.Unowned, UnownedPropertyVisitor{visitorID: playerID, propertyID: propID})
}
case TypeRailroad:
- propIndex := SpaceToOwnableProperty[nextPos]
- if PropertyOwners[propIndex] != -1 { // property owned?
- ownerID := PropertyOwners[propIndex]
- if ownerID != playerID && !IsMortgaged(propIndex) { // not by you
- OwnedRailroadVisitors = append(OwnedRailroadVisitors, OwnedRailroadVisitor{visitorID: playerID, ownerID: ownerID, railroadID: SpaceToRespProperty[nextPos]})
+ propID := ctx.getPropID(nextPos)
+ ownerID := ctx.Properties.Owners[propID.Index()].OwnerID
+ if ownerID != BankPlayerID { // property owned?
+ if ownerID != playerID && !ctx.IsMortgaged(propID) { // not by you
+ ctx.Visitors.Railroad = append(ctx.Visitors.Railroad, OwnedRailroadVisitor{visitorID: playerID, ownerID: ownerID, railroadID: prop.SubIndexID})
}
} else {
- UnownedPropertyVisitors = append(UnownedPropertyVisitors, UnownedPropertyVisitor{visitorID: playerID, propertyID: propIndex})
+ ctx.Visitors.Unowned = append(ctx.Visitors.Unowned, UnownedPropertyVisitor{visitorID: playerID, propertyID: propID})
}
case TypeUtility:
- propIndex := SpaceToOwnableProperty[nextPos]
- if PropertyOwners[propIndex] != -1 { // property owned?
- ownerID := PropertyOwners[propIndex]
- if ownerID != playerID && !IsMortgaged(propIndex) { // not by you
- OwnedUtilityVisitors = append(OwnedUtilityVisitors, OwnedUtilityVisitor{visitorID: playerID, ownerID: ownerID, utilityID: SpaceToRespProperty[nextPos], diceRoll: diceRoll})
+ propID := ctx.getPropID(nextPos)
+ ownerID := ctx.Properties.Owners[propID.Index()].OwnerID
+ if ownerID != BankPlayerID { // property owned?
+ if ownerID != playerID && !ctx.IsMortgaged(propID) { // not by you
+ ctx.Visitors.Utility = append(ctx.Visitors.Utility, OwnedUtilityVisitor{visitorID: playerID, ownerID: ownerID, utilityID: prop.SubIndexID, diceRoll: diceRoll})
}
} else {
- UnownedPropertyVisitors = append(UnownedPropertyVisitors, UnownedPropertyVisitor{visitorID: playerID, propertyID: propIndex})
+ ctx.Visitors.Unowned = append(ctx.Visitors.Unowned, UnownedPropertyVisitor{visitorID: playerID, propertyID: propID})
}
}
}
package game
-func numRailroadOwned(playerID int32) int32 {
+func (ctx *Context) numRailroadOwned(playerID PlayerID) int32 {
var ownedCount int32 = 0
- for propID, ownerID := range PropertyOwners {
- if ownerID == playerID && OwnablePropertyType[int32(propID)] == TypeRailroad {
- ownedCount++
+ for _, prop := range ctx.Properties.Owners {
+ ownerID := prop.OwnerID
+
+ if ownerID != playerID {
+ continue
+ }
+
+ spaceID := prop.SpaceID
+ space := BoardSpaces[spaceID.Index()]
+
+ propType := space.PropertyType
+ if propType != TypeRailroad {
+ continue
}
+
+ ownedCount++
+
}
return ownedCount
}
-func ProcessOwnedRailroad() {
- for _, oRV := range OwnedRailroadVisitors {
+func (ctx *Context) ProcessOwnedRailroad() {
+ for _, oRV := range ctx.Visitors.Railroad {
visitorID := oRV.visitorID
ownerID := oRV.ownerID
// railroadID := oRV.railroadID
- var rent int32 = RailroadRent[numRailroadOwned(ownerID)]
+ var rent int32 = RailroadRent[ctx.numRailroadOwned(ownerID)]
+
+ ctx.AdjustPlayerMoney(visitorID, -rent)
+ ctx.AdjustPlayerMoney(ownerID, rent)
- AdjustPlayerMoney(visitorID, -rent)
- AdjustPlayerMoney(ownerID, rent)
// reset railroad rent mod after payment
- ModifierRailroadRentMultiplier = 1
+ ctx.Turn.Modifier.RailroadRentMultiplier = 1
}
}
package game
-func ProcessTax() {
- for _, playerID := range TaxVisitors {
- taxID := SpaceToTaxSpace[Users[playerID].CurrentSpaceID]
- AdjustPlayerMoney(playerID, -TaxSpaces[taxID].Amount)
+func (ctx *Context) ProcessTax() {
+ for _, tV := range ctx.Visitors.Tax {
+ playerID := tV.visitorID
+ taxID := tV.taxID
+ ctx.AdjustPlayerMoney(playerID, -TaxSpaces[taxID].Amount)
}
}
--- /dev/null
+- TODO Trading
+ - validate no race condition
+ - prevent trading properties with buildings
+ - trade money
+ - negotiate / accept / decline / delete
+
+- TODO auction
+ - own auction
+ - bank liquidation auction
+
+- TODO Bankruptcy
+
+- TODO user auth
+
+- TODO Server Side Events (SSE) for updating game on client
package game
-type User struct {
+import (
+ "math/rand/v2"
+)
+
+type Player struct {
UUID string
Money int32
- CurrentSpaceID int32
+ CurrentSpaceID SpaceID
GetOutOfJailCards int32
+ CanMove bool
}
type PropertyType int
+type Space struct {
+ PropertyType PropertyType
+ SubIndexID int32 // FK to resp. OwnableProperty types
+}
-const (
- TypeGo PropertyType = iota
- TypeColor
- TypeChest
- TypeTax
- TypeRailroad
- TypeChance
- TypeJail
- TypeUtility
- TypeParking
- TypePolice
-)
+type PropertyStatic struct {
+ Name string
+ Price int32
+}
+
+type OwnableProperty struct {
+ OwnerID PlayerID
+ SpaceID SpaceID
+}
+
+type ColorGroup int32
+
+type ColorProperty struct {
+ Name string
+ Price int32
+ GroupID ColorGroup
+ Houses int32
+}
+
+type TaxSpace struct {
+ Name string
+ Amount int32
+}
+
+type ChanceSpaceIDs struct {
+ StCharlesPlace SpaceID
+ Go SpaceID
+ ReadingRailroad SpaceID
+ Boardwalk SpaceID
+ IllinoisAvenue SpaceID
+}
+
+type OwnedColorVisitor struct {
+ visitorID PlayerID
+ ownerID PlayerID
+ colorID int32
+}
+
+type OwnedRailroadVisitor struct {
+ visitorID PlayerID
+ ownerID PlayerID
+ railroadID int32
+}
+
+type OwnedUtilityVisitor struct {
+ visitorID PlayerID
+ ownerID PlayerID
+ utilityID int32
+ diceRoll int32
+}
+
+type UnownedPropertyVisitor struct {
+ visitorID PlayerID
+ propertyID PropertyID
+}
+
+type InJailVisitor struct {
+ visitorID PlayerID
+ TurnsLeft int32
+}
+
+type TaxVisitor struct {
+ visitorID PlayerID
+ taxID int32
+}
+
+type Visitors struct { // SubIndexID is the PK for each resp table
+ Unowned []UnownedPropertyVisitor
+ Color []OwnedColorVisitor
+ Railroad []OwnedRailroadVisitor
+ Utility []OwnedUtilityVisitor
+ Go []PlayerID
+ Tax []TaxVisitor
+ Chance []PlayerID
+ Chest []PlayerID
+ InJail []InJailVisitor
+ // Parking []PlayerID
+ // Police []PlayerID
+}
+
+// IDs
+
+type PlayerID struct {
+ id int32
+}
+
+type PropertyID struct {
+ id int32
+}
+
+type SpaceID struct {
+ id int32
+}
+
+func (p *PlayerID) Index() int {
+ return int(p.id)
+}
+
+func (p *PropertyID) Index() int {
+ return int(p.id)
+}
+
+func (s *SpaceID) Index() int {
+ return int(s.id)
+}
+
+func Add(a SpaceID, b int32) SpaceID {
+ return SpaceID{id: a.id + b}
+}
+
+// Game Context (Ctx)
+type Modifiers struct {
+ RailroadRentMultiplier int32
+ UtilityForceRentMultiplier bool
+}
+
+type Players struct {
+ Alive []Player // PlayerID is PK
+}
+
+type Turn struct {
+ Current PlayerID
+ Ended bool
+ DiceRollsRemaining int32
+ NumDiceRolled int32
+ RolledDoubles bool
+ MoveQueue []int32
+ InDebt bool
+ Modifier Modifiers
+}
+
+type Properties struct {
+ Owners []OwnableProperty // PropertyID is PK
+ Mortgages []PropertyID
+}
+
+type Context struct {
+ Random *rand.Rand
+ MoveQueue []int32
+ Players Players
+ Turn Turn // state reset every turn end
+ Visitors Visitors
+ Properties Properties
+}
--- /dev/null
+package game
+
+func (ctx *Context) ProcessUnowned() {
+ for _, uV := range ctx.Visitors.Unowned {
+ visitorID := uV.visitorID
+ propID := uV.propertyID
+
+ prop := ctx.Properties.Owners[propID.Index()]
+
+ // TODO: trigger buy or auction
+
+ // spaceID := prop.SpaceID
+ // space := BoardSpaces[spaceID.Index()]
+ //
+ // var price int32 = 0
+ //
+ // switch space.PropertyType {
+ // case TypeColor:
+ // price = ColorProperties[space.SubIndexID].Price
+ // case TypeUtility:
+ // price = UtilityPrice
+ // case TypeRailroad:
+ // price = RailroadPrice
+ // }
+ //
+ // ctx.AdjustPlayerMoney(visitorID, -price)
+
+ }
+}
package game
-func numUtilities(playerID int32) int32 {
+func (ctx *Context) NumUtilities(playerID PlayerID) int32 {
var ownedCount int32 = 0
- for propID, ownerID := range PropertyOwners {
- if ownerID == playerID && OwnablePropertyType[int32(propID)] == TypeUtility {
- ownedCount++
+ for _, prop := range ctx.Properties.Owners {
+ ownerID := prop.OwnerID
+
+ if ownerID != playerID {
+ continue
+ }
+
+ spaceID := prop.SpaceID
+ space := BoardSpaces[spaceID.Index()]
+
+ propType := space.PropertyType
+ if propType != TypeUtility {
+ continue
}
+
+ ownedCount++
+
}
return ownedCount
}
-func ProcessOwnedUtility() {
- for _, oUV := range OwnedUtilityVisitors {
+func (ctx *Context) ProcessOwnedUtility() {
+ for _, oUV := range ctx.Visitors.Utility {
visitorID := oUV.visitorID
ownerID := oUV.ownerID
// utilityID := oUV.utilityID
var rent int32 = 0
- if !ModifierUtilityForceRentMultiplier {
- rent = UtilityRentMult[numUtilities(ownerID)] * diceRoll
+ if !ctx.Turn.Modifier.UtilityForceRentMultiplier {
+ rent = UtilityRentMult[ctx.NumUtilities(ownerID)] * diceRoll
} else {
rent = 10 * diceRoll
}
- AdjustPlayerMoney(visitorID, -rent)
- AdjustPlayerMoney(ownerID, rent)
+ ctx.AdjustPlayerMoney(visitorID, -rent)
+ ctx.AdjustPlayerMoney(ownerID, rent)
- ModifierUtilityForceRentMultiplier = false
+ ctx.Turn.Modifier.UtilityForceRentMultiplier = false
}
}
"fmt"
"io"
"log"
+ "math/rand/v2"
"monopoly-web/game"
"net/http"
)
+type Room struct {
+ gameCtx *game.Context
+}
+
+func initRoom() Room {
+ randSeed := rand.NewPCG(20, 26)
+ players := []game.Player{
+ game.InitPlayer(),
+ }
+
+ return Room{
+ gameCtx: game.InitCtx(randSeed, players),
+ }
+}
+
func main() {
fmt.Println("monopoly-web backend")
- game.Users = append(game.Users, game.User{UUID: "abc", Money: 100, CurrentSpaceID: 0, GetOutOfJailCards: 0})
- fmt.Println(game.Users)
+ room := initRoom()
// register routes
http.HandleFunc("/health", healthHandler)
- http.HandleFunc("/api/v1/roll", rollDiceHandler)
- http.HandleFunc("POST /api/v1/turn", endTurnHandler)
- http.HandleFunc("POST /api/v1/exit-jail", exitJailHandler)
+ http.HandleFunc("/api/v1/roll", room.rollDiceHandler)
+ http.HandleFunc("POST /api/v1/turn", room.endTurnHandler)
+ http.HandleFunc("POST /api/v1/exit-jail", room.exitJailHandler)
// listen and serve
log.Fatal(http.ListenAndServe(":8080", nil))
const UUID = "abc" // TODO: UUID in cookie
-func rollDiceHandler(w http.ResponseWriter, req *http.Request) {
- if game.ValidateCanRoll(UUID) {
- game.RollDice()
- game.ProcessMovement()
+func (r *Room) rollDiceHandler(w http.ResponseWriter, req *http.Request) {
+ if r.gameCtx.ValidateCanRoll(UUID) {
+ r.gameCtx.RollDice()
+ r.gameCtx.ProcessMovement()
}
}
-func endTurnHandler(w http.ResponseWriter, req *http.Request) {
- if game.ValidateCanEndTurn(UUID) {
- game.EndTurn()
+func (r *Room) endTurnHandler(w http.ResponseWriter, req *http.Request) {
+ if r.gameCtx.ValidateCanEndTurn(UUID) {
+ r.gameCtx.EndTurn()
}
}
-func exitJailHandler(w http.ResponseWriter, req *http.Request) {
+func (r *Room) exitJailHandler(w http.ResponseWriter, req *http.Request) {
err := req.ParseForm()
if err != nil {
http.Error(w, "Bad Request: Failed to parse form data", http.StatusBadRequest)
return
}
- if !game.ValidateCanExitJail(UUID) {
+ if !r.gameCtx.ValidateCanExitJail(UUID) {
w.WriteHeader(http.StatusForbidden)
w.Write([]byte(`{"status": "forbidden", "message": "Not your turn or not in jail"}`))
}
switch method {
case "buyout":
- err = game.JailBuyout()
+ err = r.gameCtx.JailBuyout()
if err == game.ErrNotEnoughMoney {
http.Error(w, "error: Insufficient funds", http.StatusUnprocessableEntity)
}
case "jail_free_card":
- err = game.JailUseCard()
+ err = r.gameCtx.JailUseCard()
if err == game.ErrNotEnoughJailCards {
http.Error(w, "error: Insufficient jail cards", http.StatusUnprocessableEntity)
}