From: Skullheadx Date: Tue, 19 May 2026 05:51:37 +0000 (-0400) Subject: refactor into Context X-Git-Url: http://git.skullheadx.com/nixos/static/projects.html?a=commitdiff_plain;h=8839d7265d4fcbb9252a05135cb9806dedf90754;p=monopoly-web.git refactor into Context --- diff --git a/game/chance.go b/game/chance.go index 5874a10..bda1a54 100644 --- a/game/chance.go +++ b/game/chance.go @@ -1,93 +1,83 @@ 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 @@ -95,19 +85,19 @@ func ProcessChance() { } } - 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) } } } diff --git a/game/chest.go b/game/chest.go index 1664dc6..c43289a 100644 --- a/game/chest.go +++ b/game/chest.go @@ -1,62 +1,47 @@ 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 @@ -64,21 +49,21 @@ func ProcessChest() { } } - 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) } } } diff --git a/game/color.go b/game/color.go index c49c55a..e4b8678 100644 --- a/game/color.go +++ b/game/color.go @@ -1,20 +1,36 @@ 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 @@ -24,11 +40,11 @@ func ProcessOwnedColors() { 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) } } diff --git a/game/config.go b/game/config.go new file mode 100644 index 0000000..16fcf9d --- /dev/null +++ b/game/config.go @@ -0,0 +1,247 @@ +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.", +} diff --git a/game/game.go b/game/game.go index e147eb4..2c67838 100644 --- a/game/game.go +++ b/game/game.go @@ -4,92 +4,175 @@ import ( "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") } diff --git a/game/go.go b/game/go.go index d88505b..21d8339 100644 --- a/game/go.go +++ b/game/go.go @@ -1,9 +1,7 @@ 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) } } diff --git a/game/helpers.go b/game/helpers.go index 262d844..467e1dd 100644 --- a/game/helpers.go +++ b/game/helpers.go @@ -1,28 +1,12 @@ 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 } } diff --git a/game/jail.go b/game/jail.go index b8211a9..f7d02b2 100644 --- a/game/jail.go +++ b/game/jail.go @@ -4,39 +4,36 @@ import ( "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) } } @@ -45,10 +42,11 @@ func ProcessJail() { 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 { @@ -56,10 +54,11 @@ func JailUseCard() error { } } -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 diff --git a/game/movement.go b/game/movement.go index 8c64ec1..61772a0 100644 --- a/game/movement.go +++ b/game/movement.go @@ -1,396 +1,94 @@ 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}) } } } diff --git a/game/railroad.go b/game/railroad.go index ba79a57..7ecab91 100644 --- a/game/railroad.go +++ b/game/railroad.go @@ -1,27 +1,41 @@ 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 } } diff --git a/game/tax.go b/game/tax.go index 3c5213a..414d3f7 100644 --- a/game/tax.go +++ b/game/tax.go @@ -1,8 +1,9 @@ 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) } } diff --git a/game/todo b/game/todo new file mode 100644 index 0000000..76c8585 --- /dev/null +++ b/game/todo @@ -0,0 +1,15 @@ +- 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 diff --git a/game/types.go b/game/types.go index a4fc9e9..5ad649c 100644 --- a/game/types.go +++ b/game/types.go @@ -1,23 +1,164 @@ 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 +} diff --git a/game/unowned.go b/game/unowned.go new file mode 100644 index 0000000..bc4d164 --- /dev/null +++ b/game/unowned.go @@ -0,0 +1,29 @@ +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) + + } +} diff --git a/game/utility.go b/game/utility.go index 924fcd9..054bd6b 100644 --- a/game/utility.go +++ b/game/utility.go @@ -1,18 +1,31 @@ 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 @@ -20,15 +33,15 @@ func ProcessOwnedUtility() { 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 } } diff --git a/main.go b/main.go index 01ad29d..1c48c06 100644 --- a/main.go +++ b/main.go @@ -4,21 +4,36 @@ import ( "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)) @@ -30,27 +45,27 @@ func healthHandler(w http.ResponseWriter, req *http.Request) { 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"}`)) } @@ -59,13 +74,13 @@ func exitJailHandler(w http.ResponseWriter, req *http.Request) { 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) }